Merge "Fix missing ATOMIC_CANCELABLE in MovePage::move()"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 8 Jan 2019 21:24:48 +0000 (21:24 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 8 Jan 2019 21:24:48 +0000 (21:24 +0000)
126 files changed:
.eslintrc.json
.mailmap
CREDITS
RELEASE-NOTES-1.33
includes/Block.php
includes/DefaultSettings.php
includes/OutputPage.php
includes/Revision/SlotRecord.php
includes/Revision/SlotRoleRegistry.php
includes/Title.php
includes/api/i18n/es.json
includes/api/i18n/zh-hant.json
includes/auth/AbstractPasswordPrimaryAuthenticationProvider.php
includes/block/BlockRestriction.php
includes/block/Restriction/AbstractRestriction.php
includes/block/Restriction/PageRestriction.php
includes/block/Restriction/Restriction.php
includes/changes/ChangesList.php
includes/filerepo/file/OldLocalFile.php
includes/htmlform/HTMLSelectLanguageField.php
includes/import/ImportableUploadRevisionImporter.php
includes/installer/i18n/eu.json
includes/installer/i18n/lij.json
includes/installer/i18n/oc.json
includes/installer/i18n/ro.json
includes/installer/i18n/uk.json
includes/libs/mime/IEContentAnalyzer.php
includes/libs/objectcache/WANObjectCache.php
includes/page/WikiPage.php
includes/password/UserPasswordPolicy.php
includes/specials/SpecialBlock.php
includes/specials/SpecialUserrights.php
includes/upload/UploadBase.php
includes/user/User.php
includes/utils/ZipDirectoryReader.php
includes/widget/CheckMatrixWidget.php
languages/i18n/be-tarask.json
languages/i18n/be.json
languages/i18n/bqi.json
languages/i18n/cs.json
languages/i18n/de.json
languages/i18n/diq.json
languages/i18n/en.json
languages/i18n/es.json
languages/i18n/exif/be-tarask.json
languages/i18n/exif/ia.json
languages/i18n/exif/uk.json
languages/i18n/fr.json
languages/i18n/gor.json
languages/i18n/he.json
languages/i18n/hu.json
languages/i18n/min.json
languages/i18n/qqq.json
languages/i18n/ses.json
languages/i18n/th.json
languages/i18n/uz.json
languages/i18n/yi.json
languages/i18n/zh-hans.json
maintenance/resources/foreign-resources.yaml
mw-config/config.js
package.json
resources/Resources.php
resources/lib/qunitjs/qunit.css
resources/lib/qunitjs/qunit.js
resources/src/jquery.tablesorter/jquery.tablesorter.js
resources/src/jquery/jquery.checkboxShiftClick.js
resources/src/jquery/jquery.makeCollapsible.js
resources/src/jquery/jquery.suggestions.js
resources/src/jquery/jquery.textSelection.js
resources/src/mediawiki.action/mediawiki.action.edit.editWarning.js
resources/src/mediawiki.action/mediawiki.action.edit.js
resources/src/mediawiki.action/mediawiki.action.edit.preview.js
resources/src/mediawiki.action/mediawiki.action.history.js
resources/src/mediawiki.action/mediawiki.action.view.dblClickEdit.js
resources/src/mediawiki.action/mediawiki.action.view.postEdit.js
resources/src/mediawiki.action/mediawiki.action.view.rightClickEdit.js
resources/src/mediawiki.api/upload.js
resources/src/mediawiki.checkboxtoggle.js
resources/src/mediawiki.debug/debug.js
resources/src/mediawiki.filewarning/filewarning.js
resources/src/mediawiki.htmlform.checker.js
resources/src/mediawiki.htmlform/cloner.js
resources/src/mediawiki.htmlform/multiselect.js
resources/src/mediawiki.htmlform/selectandother.js
resources/src/mediawiki.jqueryMsg/mediawiki.jqueryMsg.js
resources/src/mediawiki.legacy/protect.js
resources/src/mediawiki.notification/notification.js
resources/src/mediawiki.page.gallery.js
resources/src/mediawiki.page.ready.js
resources/src/mediawiki.page.watch.ajax.js
resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.FiltersViewModel.js
resources/src/mediawiki.rcfilters/dm/mw.rcfilters.dm.SavedQueriesModel.js
resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js
resources/src/mediawiki.rcfilters/mw.rcfilters.init.js
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.ChangesListWrapperWidget.js
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.FilterTagMultiselectWidget.js
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.SavedLinksListWidget.js
resources/src/mediawiki.special.apisandbox/apisandbox.js
resources/src/mediawiki.special.block.js
resources/src/mediawiki.special.import.js
resources/src/mediawiki.special.recentchanges.js
resources/src/mediawiki.special.search.commonsInterwikiWidget.js
resources/src/mediawiki.special.search/search.js
resources/src/mediawiki.special.undelete.js
resources/src/mediawiki.special.unwatchedPages/unwatchedPages.js
resources/src/mediawiki.special.upload/upload.js
resources/src/mediawiki.special.userrights.js
resources/src/mediawiki.special.watchlist/watchlist.js
resources/src/mediawiki.special/block.less
resources/src/mediawiki.toc/toc.js
resources/src/mediawiki.widgets.datetime/DateTimeInputWidget.js
resources/src/mediawiki.widgets/MediaSearch/mw.widgets.APIResultsQueue.js
resources/src/mediawiki.widgets/MediaSearch/mw.widgets.MediaResourceProvider.js
resources/src/mediawiki.widgets/mw.widgets.DateInputWidget.js
resources/src/mediawiki.widgets/mw.widgets.SearchInputWidget.js
tests/phpunit/includes/TitlePermissionTest.php
tests/phpunit/includes/auth/AbstractPasswordPrimaryAuthenticationProviderTest.php
tests/phpunit/includes/auth/LocalPasswordPrimaryAuthenticationProviderTest.php
tests/phpunit/includes/password/UserPasswordPolicyTest.php
tests/phpunit/includes/upload/UploadBaseTest.php
tests/qunit/.eslintrc.json
tests/qunit/data/testrunner.js
tests/qunit/suites/resources/jquery/jquery.color.test.js
tests/qunit/suites/resources/jquery/jquery.tablesorter.test.js
tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js
tests/qunit/suites/resources/mediawiki/mediawiki.toc.test.js

index 97f7c31..0c0a7b5 100644 (file)
@@ -10,6 +10,8 @@
                "OO": false
        },
        "rules": {
-               "max-len": 0
+               "quote-props": [ "error", "as-needed" ],
+               "max-len": "off",
+               "jquery/no-global-selector": "off"
        }
 }
index 8f2aa28..1265bd2 100644 (file)
--- a/.mailmap
+++ b/.mailmap
@@ -493,3 +493,5 @@ Zppix <support@zppixballee.com>
 Étienne Beaulé <beauleetienne0@gmail.com>
 Željko Filipin <zeljko.filipin@gmail.com>
 Željko Filipin <zeljko.filipin@gmail.com> <zfilipin@wikimedia.org>
+星耀晨曦 <razesoldier@outlook.com>
+星耀晨曦 <razesoldier@outlook.com> <liguangjie4399@hotmail.com>
diff --git a/CREDITS b/CREDITS
index 5e19ca0..319b566 100644 (file)
--- a/CREDITS
+++ b/CREDITS
@@ -589,7 +589,6 @@ The following list can be found parsed under Special:Version/Credits -->
 * rahul21
 * Raimond Spekking
 * Ramunas Geciauskas
-* RazeSoldier
 * Remember the dot
 * René Kijewski
 * Reza
index 8533741..c0dd84f 100644 (file)
@@ -54,6 +54,7 @@ production.
 * Updated wikimedia/ip-set from 1.2.0 to 2.0.0.
   * The deprecated IPSet\IPSet alias was removed, Wikimedia\IPSet must be
     used instead.
+* Updated qunitjs from 2.6.2 to 2.9.1.
 * …
 
 ==== Removed external libraries ====
@@ -196,6 +197,10 @@ because of Phabricator reports.
 * The PasswordPolicy 'PasswordCannotBePopular' has been deprecated. To
   follow best practices, it is reccommended to use 'PasswordNotInLargeBlacklist'
   instead which blacklists 100,000 commonly used passwords.
+* (T208862) Action::requiresUnblock() is now called from
+  Title::getUserPermissionsErrors() and Title::userCan(). Previously, the method
+  was only called in Action::checkCanExecute(). Actions should ensure that their
+  requiresUnblock() returns the proper result (the default is `true`).
 * …
 
 === Other changes in 1.33 ===
index ec8cae8..fb3caf6 100644 (file)
@@ -1132,6 +1132,7 @@ class Block {
         * prohibited from editing any page on the site (other than their own talk
         * page).
         *
+        * @since 1.33
         * @param null|bool $x
         * @return bool
         */
@@ -1728,6 +1729,7 @@ class Block {
        /**
         * Get block information used in different block error messages
         *
+        * @since 1.33
         * @param IContextSource $context
         * @return array
         */
@@ -1769,6 +1771,7 @@ class Block {
         * Getting the restrictions will perform a database query if the restrictions
         * are not already loaded.
         *
+        * @since 1.33
         * @return Restriction[]
         */
        public function getRestrictions() {
@@ -1787,8 +1790,8 @@ class Block {
        /**
         * Set Restrictions.
         *
+        * @since 1.33
         * @param Restriction[] $restrictions
-        *
         * @return self
         */
        public function setRestrictions( array $restrictions ) {
index f7c3fce..9df4ab6 100644 (file)
@@ -4454,13 +4454,20 @@ $wgCentralIdLookupProvider = 'local';
  * Password policy for the wiki.
  * Structured as
  * [
- *     'policies' => [ <group> => [ <policy> => <value>, ... ], ... ],
+ *     'policies' => [ <group> => [ <policy> => <settings>, ... ], ... ],
  *     'checks' => [ <policy> => <callback>, ... ],
  * ]
  * where <group> is a user group, <policy> is a password policy name
  * (arbitrary string) defined in the 'checks' part, <callback> is the
- * PHP callable implementing the policy check, <value> is a number,
- * boolean or null that gets passed to the callback.
+ * PHP callable implementing the policy check, <settings> is an array
+ * of options with the following keys:
+ * - value: (number, boolean or null) the value to pass to the callback
+ * - forceChange: (bool, default false) if the password is invalid, do
+ *   not let the user log in without changing the password
+ * As a shorthand for [ 'value' => <value> ], simply <value> can be written.
+ * When multiple password policies are defined for a user, the settings
+ * arrays are merged, and for fields which are set in both arrays, the
+ * larger value (as understood by PHP's 'max' method) is taken.
  *
  * A user's effective policy is the superset of all policy statements
  * from the policies for the groups where the user is a member. If more
@@ -9013,8 +9020,8 @@ $wgActorTableSchemaMigrationStage = SCHEMA_COMPAT_OLD;
  * Flag to enable Partial Blocks. This allows an admin to prevent a user from editing specific pages
  * or namespaces.
  *
- * @since 1.32
- * @deprecated 1.32
+ * @since 1.33
+ * @deprecated 1.33
  * @var bool
  */
 $wgEnablePartialBlocks = false;
index 02e13e7..61a1ef2 100644 (file)
@@ -4134,12 +4134,12 @@ class OutputPage extends ContextSource {
         *
         * For example:
         *
-        *    $wgOut->wrapWikiMsg( "<div class='error'>\n$1\n</div>", 'some-error' );
+        *     $wgOut->wrapWikiMsg( "<div class='error'>\n$1\n</div>", 'some-error' );
         *
         * Is equivalent to:
         *
-        *    $wgOut->addWikiTextAsInterface( "<div class='error'>\n"
-        *        . wfMessage( 'some-error' )->plain() . "\n</div>" );
+        *     $wgOut->addWikiTextAsInterface( "<div class='error'>\n"
+        *         . wfMessage( 'some-error' )->plain() . "\n</div>" );
         *
         * The newline after the opening div is needed in some wikitext. See T21226.
         *
index 89980f4..064f7a4 100644 (file)
@@ -219,8 +219,6 @@ class SlotRecord {
        }
 
        /**
-        * SlotRecord constructor.
-        *
         * The following fields are supported by the $row parameter:
         *
         *   $row->blob_data
index b108b98..41ea4a6 100644 (file)
@@ -63,8 +63,6 @@ class SlotRoleRegistry {
        private $handlers;
 
        /**
-        * SlotRoleRegistry constructor.
-        *
         * @param NameTableStore $roleNamesStore
         */
        public function __construct( NameTableStore $roleNamesStore ) {
index 909f528..3496668 100644 (file)
@@ -36,7 +36,7 @@ use MediaWiki\MediaWikiServices;
  * @note Consider using a TitleValue object instead. TitleValue is more lightweight
  *       and does not rely on global state or the database.
  */
-class Title implements LinkTarget {
+class Title implements LinkTarget, IDBAccessObject {
        /** @var MapCacheLRU */
        static private $titleCache = null;
 
@@ -2691,14 +2691,34 @@ class Title implements LinkTarget {
                }
 
                $useReplica = ( $rigor !== 'secure' );
-               if ( ( $action == 'edit' || $action == 'create' )
-                       && !$user->isBlockedFrom( $this, $useReplica )
-               ) {
-                       // Don't block the user from editing their own talk page unless they've been
-                       // explicitly blocked from that too.
-               } elseif ( $user->isBlocked() && $user->getBlock()->prevents( $action ) !== false ) {
+               $block = $user->getBlock( $useReplica );
+
+               // The block may explicitly allow an action (like "read" or "upload").
+               if ( $block && $block->prevents( $action ) === false ) {
+                       return $errors;
+               }
+
+               // Determine if the user is blocked from this action on this page.
+               // What gets passed into this method is a user right, not an action nmae.
+               // There is no way to instantiate an action by restriction. However, this
+               // will get the action where the restriction is the same. This may result
+               // in actions being blocked that shouldn't be.
+               if ( Action::exists( $action ) ) {
                        // @todo FIXME: Pass the relevant context into this function.
-                       $errors[] = $user->getBlock()->getPermissionsError( RequestContext::getMain() );
+                       $action = Action::factory( $action, WikiPage::factory( $this ), RequestContext::getMain() );
+               } else {
+                       $action = null;
+               }
+
+               // If no action object is returned, assume that the action requires unblock
+               // which is the default.
+               if ( !$action || $action->requiresUnblock() ) {
+                       if ( $user->isBlockedFrom( $this, $useReplica ) ) {
+                               // @todo FIXME: Pass the relevant context into this function.
+                               $errors[] = $block
+                                       ? $block->getPermissionsError( RequestContext::getMain() )
+                                       : [ 'actionblockedtext' ];
+                       }
                }
 
                return $errors;
@@ -3278,13 +3298,12 @@ class Title implements LinkTarget {
         * indicating who can move or edit the page from the page table, (pre 1.10) rows.
         * Edit and move sections are separated by a colon
         * Example: "edit=autoconfirmed,sysop:move=sysop"
-        * @param bool $readLatest When true, skip replicas and read from the master DB.
         */
-       public function loadRestrictionsFromRows(
-               $rows, $oldFashionedRestrictions = null, $readLatest = false
-       ) {
-               $whichDb = $readLatest ? DB_MASTER : DB_REPLICA;
-               $dbr = wfGetDB( $whichDb );
+       public function loadRestrictionsFromRows( $rows, $oldFashionedRestrictions = null ) {
+               // This function will only read rows from a table that we migrated away
+               // from before adding READ_LATEST support to loadRestrictions, so we
+               // don't need to support reading from DB_MASTER here.
+               $dbr = wfGetDB( DB_REPLICA );
 
                $restrictionTypes = $this->getRestrictionTypes();
 
@@ -3354,39 +3373,52 @@ class Title implements LinkTarget {
         * indicating who can move or edit the page from the page table, (pre 1.10) rows.
         * Edit and move sections are separated by a colon
         * Example: "edit=autoconfirmed,sysop:move=sysop"
-        * @param bool $readLatest When true, skip replicas and read from the master DB.
+        * @param int $flags A bit field. If self::READ_LATEST is set, skip replicas and read
+        *  from the master DB.
         */
-       public function loadRestrictions( $oldFashionedRestrictions = null, $readLatest = false ) {
+       public function loadRestrictions( $oldFashionedRestrictions = null, $flags = 0 ) {
+               $readLatest = DBAccessObjectUtils::hasFlags( $flags, self::READ_LATEST );
                if ( $this->mRestrictionsLoaded && !$readLatest ) {
                        return;
                }
 
+               // TODO: should probably pass $flags into getArticleID, but it seems hacky
+               // to mix READ_LATEST and GAID_FOR_UPDATE, even if they have the same value.
+               // Maybe deprecate GAID_FOR_UPDATE now that we implement IDBAccessObject?
                $id = $this->getArticleID();
                if ( $id ) {
-                       $cache = ObjectCache::getMainWANInstance();
                        $fname = __METHOD__;
-                       $rows = $cache->getWithSetCallback(
-                               // Page protections always leave a new null revision
-                               $cache->makeKey( 'page-restrictions', $id, $this->getLatestRevID(), $readLatest ),
-                               $cache::TTL_DAY,
-                               function ( $curValue, &$ttl, array &$setOpts ) use ( $fname, $readLatest ) {
-                                       $whichDb = $readLatest ? DB_MASTER : DB_REPLICA;
-                                       $dbr = wfGetDB( $whichDb );
-
-                                       $setOpts += Database::getCacheSetOptions( $dbr );
-
-                                       return iterator_to_array(
-                                               $dbr->select(
-                                                       'page_restrictions',
-                                                       [ 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ],
-                                                       [ 'pr_page' => $this->getArticleID() ],
-                                                       $fname
-                                               )
-                                       );
-                               }
-                       );
+                       $loadRestrictionsFromDb = function ( Database $dbr ) use ( $fname, $id ) {
+                               return iterator_to_array(
+                                       $dbr->select(
+                                               'page_restrictions',
+                                               [ 'pr_type', 'pr_expiry', 'pr_level', 'pr_cascade' ],
+                                               [ 'pr_page' => $id ],
+                                               $fname
+                                       )
+                               );
+                       };
+
+                       if ( $readLatest ) {
+                               $dbr = wfGetDB( DB_MASTER );
+                               $rows = $loadRestrictionsFromDb( $dbr );
+                       } else {
+                               $cache = ObjectCache::getMainWANInstance();
+                               $rows = $cache->getWithSetCallback(
+                                       // Page protections always leave a new null revision
+                                       $cache->makeKey( 'page-restrictions', $id, $this->getLatestRevID() ),
+                                       $cache::TTL_DAY,
+                                       function ( $curValue, &$ttl, array &$setOpts ) use ( $loadRestrictionsFromDb ) {
+                                               $dbr = wfGetDB( DB_REPLICA );
+
+                                               $setOpts += Database::getCacheSetOptions( $dbr );
+
+                                               return $loadRestrictionsFromDb( $dbr );
+                                       }
+                               );
+                       }
 
-                       $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions, $readLatest );
+                       $this->loadRestrictionsFromRows( $rows, $oldFashionedRestrictions );
                } else {
                        $title_protection = $this->getTitleProtectionInternal();
 
index 3f331eb..224eca5 100644 (file)
        "apihelp-query+filearchive-example-simple": "Mostrar una lista de todos los archivos eliminados.",
        "apihelp-query+filerepoinfo-summary": "Devuelve metainformación sobre los repositorios de imágenes configurados en el wiki.",
        "apihelp-query+filerepoinfo-param-prop": "Qué propiedades del repositorio obtener (las propiedades disponibles pueden variar en otras wikis).",
+       "apihelp-query+filerepoinfo-paramvalue-prop-rootUrl": "Ruta de la URL raíz para las rutas de las imágenes.",
+       "apihelp-query+filerepoinfo-paramvalue-prop-scriptDirUrl": "Ruta de la URL raíz para la instalación MediaWiki del wiki del repositorio.",
+       "apihelp-query+filerepoinfo-paramvalue-prop-server": "<var>[[mw:Special:MyLanguage/Manual:$wgServer|$wgServer]]</var> o equivalente del wiki del repositorio.",
+       "apihelp-query+filerepoinfo-paramvalue-prop-thumbUrl": "Ruta de la URL raíz para las rutas de las miniaturas.",
+       "apihelp-query+filerepoinfo-paramvalue-prop-url": "Ruta de la URL de la zona pública.",
        "apihelp-query+filerepoinfo-example-simple": "Obtener información acerca de los repositorios de archivos.",
        "apihelp-query+fileusage-summary": "Encontrar todas las páginas que utilizan los archivos dados.",
        "apihelp-query+fileusage-param-prop": "Qué propiedades se obtendrán:",
index bf77505..e1cc8e2 100644 (file)
@@ -31,6 +31,7 @@
        "apihelp-main-param-servedby": "在結果中包括提出請求的主機名。",
        "apihelp-main-param-curtimestamp": "在結果中包括目前的時間戳。",
        "apihelp-main-param-responselanginfo": "在結果中包括<var>uselang</var>和<var>errorlang</var>所用的語言。",
+       "apihelp-main-param-errorformat": "用於警告和錯誤文字輸出的格式。\n; plaintext:移除掉 HTML 標籤,且實體已替換的 wiki 文字。\n; wikitext:未解析的 wiki 文字。\n; html:HTML。\n; raw:訊息鍵值與參數。\n; none:無文字輸出,僅含有錯誤代碼。\n; bc:用於 MediaWiki 1.29 之前版本的格式。會忽略 <var>errorlang</var> 與 <var>errorsuselocal</var>。",
        "apihelp-main-param-errorsuselocal": "若有指定,錯誤文字會使用來自 {{ns:MediaWiki}} 命名空間的本地自定義訊息。",
        "apihelp-block-summary": "封鎖使用者。",
        "apihelp-block-param-user": "要封鎖的使用者名稱、IP 位址或 IP 範圍。不能與 <var>$1userid</var> 一起使用",
@@ -68,6 +69,7 @@
        "apihelp-compare-param-fromid": "要比對的第一個頁面 ID。",
        "apihelp-compare-param-fromrev": "要比對的第一個修訂。",
        "apihelp-compare-param-frompst": "在 <var>fromtext-&#x7B;slot}</var> 進行預先儲存轉換。",
+       "apihelp-compare-param-fromslots": "覆蓋由 <var>fromtitle</var>、<var>fromid</var> 或 <var>fromrev</var> 指定的修訂內容。\n\n此參數指定要變動的間隔。使用 <var>fromtext-&#x7B;slot}</var>、<var>fromcontentmodel-&#x7B;slot}</var>、與 <var>fromcontentformat-&#x7B;slot}</var> 來指定各間隔的內容。",
        "apihelp-compare-param-fromcontentmodel-{slot}": "<var>fromtext-&#x7B;slot}</var> 內容模組。若不提供,則會根據其它參數猜測。",
        "apihelp-compare-param-fromcontentformat-{slot}": "<var>fromtext-&#x7B;slot}</var> 的內容序列化格式。",
        "apihelp-compare-param-fromtext": "指定 <kbd>fromslots=main</kbd> 並改用 <var>fromtext-main</var>。",
        "apihelp-expandtemplates-param-title": "頁面標題。",
        "apihelp-expandtemplates-param-text": "要轉換的 Wikitext。",
        "apihelp-expandtemplates-param-revid": "修訂 ID,用於 <code><nowiki>{{REVISIONID}}</nowiki></code> 和相似變數。",
+       "apihelp-expandtemplates-param-prop": "所要取得的資訊部份。\n\n請注意若沒有選定值,結果會包含 wiki 文字,輸出內容會採用棄用格式。",
        "apihelp-expandtemplates-paramvalue-prop-wikitext": "展開的 wiki 文字。",
+       "apihelp-expandtemplates-paramvalue-prop-categories": "任何呈現在輸入中,且未在 wiki 文字輸出裡表現出的分類。",
        "apihelp-expandtemplates-paramvalue-prop-properties": "透過在 wiki 文字裡擴充魔術字所定義的頁面屬性。",
        "apihelp-expandtemplates-paramvalue-prop-volatile": "輸出內容是否易變,且是否不應在頁面其它位置裡重複使用。",
+       "apihelp-expandtemplates-paramvalue-prop-ttl": "結果的快取應失效後的最長時間。",
        "apihelp-expandtemplates-paramvalue-prop-modules": "已請求添加至輸出內容之解析器功能的任何 ResourceLoader 模組。要載入請使用 <code>mw.loader.using()</code>。<kbd>jsconfigvars</kbd> 或 <kbd>encodedjsconfigvars</kbd> 其一必須與 <kbd>modules</kbd> 一同被請求。",
        "apihelp-expandtemplates-paramvalue-prop-jsconfigvars": "指定頁面的 JavaScript 設置變量。",
        "apihelp-expandtemplates-paramvalue-prop-encodedjsconfigvars": "指定頁面的 JavaScript 設置變量為 JSON 字串。",
        "apihelp-feedrecentchanges-param-hidecategorization": "隱藏分類成員更改。",
        "apihelp-feedrecentchanges-param-tagfilter": "按標籤篩選。",
        "apihelp-feedrecentchanges-param-target": "僅顯示從該頁面所連結頁面上的變更。",
+       "apihelp-feedrecentchanges-param-showlinkedto": "改成顯示出連結到所選頁面的那些頁面之變更。",
        "apihelp-feedrecentchanges-example-simple": "顯示近期變更。",
        "apihelp-feedrecentchanges-example-30days": "顯示近期30天內的變動",
        "apihelp-feedwatchlist-summary": "返回監視清單摘要。",
        "apihelp-filerevert-example-revert": "回退 <kbd>Wiki.png</kbd> 至 <kbd>2011-03-05T15:27:40Z</kbd> 的版本。",
        "apihelp-help-summary": "顯示指定模組的說明。",
        "apihelp-help-param-modules": "顯示說明的模組(<var>action</var> 與 <var>format</var> 參數的值、或 <kbd>main</kbd>)。可透過 <kbd>+</kbd> 來指定子模組。",
+       "apihelp-help-param-submodules": "包含用於命名模組之子模組的說明。",
+       "apihelp-help-param-recursivesubmodules": "包含遞迴子模組的說明。",
        "apihelp-help-param-helpformat": "說明輸出的格式。",
        "apihelp-help-param-wrap": "在標準 API 回應結構裡包裹輸出。",
        "apihelp-help-param-toc": "在 HTML 輸出裡包含目錄。",
        "apihelp-query+blocks-example-users": "列出使用者 <kbd>Alice</kbd> 與 <kbd>Bob</kbd> 的封鎖。",
        "apihelp-query+categories-summary": "列出頁面隸屬的所有分類。",
        "apihelp-query+categories-param-prop": "為各分類所要取得的額外屬性:",
+       "apihelp-query+categories-paramvalue-prop-sortkey": "添加用於分類的排序鍵值(十六進位字串)與排序鍵值字首(人類可讀的部份)。",
        "apihelp-query+categories-paramvalue-prop-timestamp": "添加當添加分類時的時間戳記。",
        "apihelp-query+categories-paramvalue-prop-hidden": "標記由 <code>_&#95;HIDDENCAT_&#95;</code> 隱藏的分類。",
        "apihelp-query+categories-param-show": "要顯示出的分類種類。",
        "apihelp-query+categorymembers-param-prop": "要包含的資訊部份:",
        "apihelp-query+categorymembers-paramvalue-prop-ids": "添加頁面 ID。",
        "apihelp-query+categorymembers-paramvalue-prop-title": "添加標題與頁面的命名空間 ID。",
-       "apihelp-query+categorymembers-paramvalue-prop-sortkey": "添加使用來在分類裡排序的排序鍵(十六進位字串)。",
+       "apihelp-query+categorymembers-paramvalue-prop-sortkey": "添加使用來在分類裡排序的排序鍵值(十六進位字串)。",
+       "apihelp-query+categorymembers-paramvalue-prop-sortkeyprefix": "添加用於在分類裡排序的排序鍵值字首(排序鍵值中人類可讀的部份)。",
        "apihelp-query+categorymembers-paramvalue-prop-type": "添加頁面已被分類的類型(<samp>page</samp>、<samp>subcat</samp> 或 <samp>file</samp>)。",
        "apihelp-query+categorymembers-paramvalue-prop-timestamp": "添加在頁面有被包含時的時間戳記。",
        "apihelp-query+categorymembers-param-namespace": "僅包含在這些命名空間的頁面。請注意可能會使用 <kbd>$1type=subcat</kbd> 或 <kbd>$1type=file</kbd>,而非 <kbd>$1namespace=14</kbd> 或 <kbd>6</kbd>。",
        "apihelp-query+categorymembers-param-dir": "排序的方向。",
        "apihelp-query+categorymembers-param-start": "起始列出的時間戳記。僅能與 <kbd>$1sort=timestamp</kbd> 一起使用。",
        "apihelp-query+categorymembers-param-end": "結束列出的時間戳記。僅能與 <kbd>$1sort=timestamp</kbd> 一起使用。",
+       "apihelp-query+categorymembers-param-starthexsortkey": "開始列出的排序鍵值,由 <kbd>$1prop=sortkey</kbd> 所回傳。僅能與 <kbd>$1sort=sortkey</kbd> 一起使用。",
+       "apihelp-query+categorymembers-param-endhexsortkey": "終止列出的排序鍵值,由 <kbd>$1prop=sortkey</kbd> 所回傳。僅能與 <kbd>$1sort=sortkey</kbd> 一起使用。",
+       "apihelp-query+categorymembers-param-startsortkeyprefix": "開始列出的排序鍵值字首,僅能與 <kbd>$1sort=sortkey</kbd> 一起使用。覆蓋 <var>$1starthexsortkey</var>。",
+       "apihelp-query+categorymembers-param-endsortkeyprefix": "終止列出 <strong>before</strong> 的排序鍵值字首(並不是 <strong>at</strong>,若此值有出現將不會被包含到!),僅能與 $1sort=sortkey 一起使用。覆蓋 $1endhexsortkey。",
        "apihelp-query+categorymembers-param-startsortkey": "請改用 $1starthexsortkey。",
        "apihelp-query+categorymembers-param-endsortkey": "請改用 $1endhexsortkey。",
        "apihelp-query+categorymembers-example-simple": "取得在 <kbd>Category:Physics</kbd> 裡前 10 項的頁面。",
        "apihelp-query+contributors-param-limit": "要回傳的貢獻人員數量。",
        "apihelp-query+contributors-example-simple": "顯示頁面 <kbd>Main Page</kbd> 的貢獻者。",
        "apihelp-query+deletedrevisions-summary": "取得已刪除修訂的資訊。",
+       "apihelp-query+deletedrevisions-param-start": "要開始列舉的時間戳記。當處理修訂 ID 清單時會被忽略。",
+       "apihelp-query+deletedrevisions-param-end": "要停止列舉的時間戳記。當處理修訂 ID 清單時會被忽略。",
        "apihelp-query+deletedrevisions-param-tag": "僅列出以此標籤所標記的修訂。",
        "apihelp-query+deletedrevisions-param-user": "此列出由該使用者作出的修訂。",
        "apihelp-query+deletedrevisions-param-excludeuser": "不要列出由該使用者作出的修訂。",
        "apihelp-query+recentchanges-paramvalue-prop-autopatrolled": "標記可巡查編輯為自動巡查或否。",
        "apihelp-query+recentchanges-paramvalue-prop-loginfo": "添加日誌資訊(日誌 ID、日誌類型、其它)至日誌項目。",
        "apihelp-query+recentchanges-paramvalue-prop-tags": "列出項目的標籤。",
+       "apihelp-query+recentchanges-paramvalue-prop-sha1": "替與修訂關聯的項目添加內容核對和。",
        "apihelp-query+recentchanges-param-token": "請改用 <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>。",
        "apihelp-query+recentchanges-param-show": "僅顯示符合這些標準的項目。例如,僅查看由登入使用者做出的小編輯,請設定 $1show=minor|!anon。",
        "apihelp-query+recentchanges-param-limit": "要回傳變更總數。",
        "apihelp-query+redirects-example-generator": "取得所有重新導向至 [[Main Page]] 的資訊。",
        "apihelp-query+revisions-summary": "取得修訂的資訊。",
        "apihelp-query+revisions-paraminfo-singlepageonly": "僅能在單一頁面使用(模式 #2)。",
+       "apihelp-query+revisions-param-startid": "從這個修訂時間戳記開始列舉。修訂必須要存在,但不需屬於此頁面。",
+       "apihelp-query+revisions-param-endid": "在這個修訂時間戳記停止列舉。修訂必須要存在,但不需屬於此頁面。",
+       "apihelp-query+revisions-param-start": "從哪個修訂時間戳記來開始列舉。",
        "apihelp-query+revisions-param-end": "列舉至此的時間戳記。",
        "apihelp-query+revisions-param-user": "僅包含由使用者做出的修訂。",
        "apihelp-query+revisions-param-excludeuser": "不包含由使用者做出的修訂。",
        "apihelp-query+search-param-what": "要執行的搜尋類型。",
        "apihelp-query+search-param-info": "要回傳的詮釋資料。",
        "apihelp-query+search-param-prop": "要回傳的屬性:",
+       "apihelp-query+search-param-qiprofile": "要使用的查詢獨立配置(會影響排序演算法)。",
        "apihelp-query+search-paramvalue-prop-size": "添加以位元組為單位的頁面大小。",
        "apihelp-query+search-paramvalue-prop-wordcount": "添加頁面的字數。",
        "apihelp-query+search-paramvalue-prop-timestamp": "添加頁面自上一次編輯的時間戳記。",
index 4096f19..b6fedcd 100644 (file)
@@ -121,9 +121,10 @@ abstract class AbstractPasswordPrimaryAuthenticationProvider
                $reset = $this->getPasswordResetData( $username, $data );
 
                if ( !$reset && $this->config->get( 'InvalidPasswordReset' ) && !$status->isGood() ) {
+                       $hard = $status->getValue()['forceChange'] ?? false;
                        $reset = (object)[
-                               'msg' => $status->getMessage( 'resetpass-validity-soft' ),
-                               'hard' => false,
+                               'msg' => $status->getMessage( $hard ? 'resetpass-validity' : 'resetpass-validity-soft' ),
+                               'hard' => $hard,
                        ];
                }
 
index 5bf286d..5fe9650 100644 (file)
@@ -32,6 +32,7 @@ class BlockRestriction {
        /**
         * Retrieves the restrictions from the database by block id.
         *
+        * @since 1.33
         * @param int|array $blockId
         * @param IDatabase|null $db
         * @return Restriction[]
@@ -58,6 +59,7 @@ class BlockRestriction {
        /**
         * Inserts the restrictions into the database.
         *
+        * @since 1.33
         * @param Restriction[] $restrictions
         * @return bool
         */
@@ -92,6 +94,7 @@ class BlockRestriction {
         * Updates the list of restrictions. This method does not allow removing all
         * of the restrictions. To do that, use ::deleteByBlockId().
         *
+        * @since 1.33
         * @param Restriction[] $restrictions
         * @return bool
         */
@@ -156,6 +159,7 @@ class BlockRestriction {
        /**
         * Updates the list of restrictions by parent id.
         *
+        * @since 1.33
         * @param int $parentBlockId
         * @param Restriction[] $restrictions
         * @return bool
@@ -195,6 +199,7 @@ class BlockRestriction {
        /**
         * Delete the restrictions.
         *
+        * @since 1.33
         * @param Restriction[]|null $restrictions
         * @throws MWException
         * @return bool
@@ -224,6 +229,7 @@ class BlockRestriction {
        /**
         * Delete the restrictions by Block ID.
         *
+        * @since 1.33
         * @param int|array $blockId
         * @throws MWException
         * @return bool
@@ -240,6 +246,7 @@ class BlockRestriction {
        /**
         * Delete the restrictions by Parent Block ID.
         *
+        * @since 1.33
         * @param int|array $parentBlockId
         * @throws MWException
         * @return bool
@@ -261,6 +268,7 @@ class BlockRestriction {
         * equality check as the restrictions do not have to contain the same block
         * ids.
         *
+        * @since 1.33
         * @param Restriction[] $a
         * @param Restriction[] $b
         * @return bool
@@ -305,6 +313,7 @@ class BlockRestriction {
        /**
         * Set the blockId on a set of restrictions and return a new set.
         *
+        * @since 1.33
         * @param int $blockId
         * @param Restriction[] $restrictions
         * @return Restriction[]
index 88a6a0f..8c3e27f 100644 (file)
@@ -37,6 +37,7 @@ abstract class AbstractRestriction implements Restriction {
        /**
         * Create Restriction.
         *
+        * @since 1.33
         * @param int $blockId
         * @param int $value
         */
index 209b148..b72baea 100644 (file)
@@ -56,6 +56,7 @@ class PageRestriction extends AbstractRestriction {
        /**
         * Set the title.
         *
+        * @since 1.33
         * @param \Title $title
         * @return self
         */
@@ -68,6 +69,7 @@ class PageRestriction extends AbstractRestriction {
        /**
         * Get Title.
         *
+        * @since 1.33
         * @return \Title|null
         */
        public function getTitle() {
index 5fefecc..a89ca38 100644 (file)
@@ -27,6 +27,7 @@ interface Restriction {
        /**
         * Gets the id of the block.
         *
+        * @since 1.33
         * @return int
         */
        public function getBlockId();
@@ -34,6 +35,7 @@ interface Restriction {
        /**
         * Sets the id of the block.
         *
+        * @since 1.33
         * @param int $blockId
         * @return self
         */
@@ -42,6 +44,7 @@ interface Restriction {
        /**
         * Gets the value of the restriction.
         *
+        * @since 1.33
         * @return int
         */
        public function getValue();
@@ -49,6 +52,7 @@ interface Restriction {
        /**
         * Gets the type of restriction
         *
+        * @since 1.33
         * @return string
         */
        public function getType();
@@ -56,6 +60,7 @@ interface Restriction {
        /**
         * Gets the id of the type of restriction. This id is used in the database.
         *
+        * @since 1.33
         * @return string
         */
        public function getTypeId();
@@ -63,6 +68,7 @@ interface Restriction {
        /**
         * Creates a new Restriction from a database row.
         *
+        * @since 1.33
         * @param \stdClass $row
         * @return self
         */
@@ -71,6 +77,7 @@ interface Restriction {
        /**
         * Convert a restriction object into a row array for insertion.
         *
+        * @since 1.33
         * @return array
         */
        public function toRow();
@@ -78,6 +85,7 @@ interface Restriction {
        /**
         * Determine if a restriction matches a given title.
         *
+        * @since 1.33
         * @param \Title $title
         * @return bool
         */
@@ -86,6 +94,7 @@ interface Restriction {
        /**
         * Determine if a restriction equals another restriction.
         *
+        * @since 1.33
         * @param Restriction $other
         * @return bool
         */
@@ -94,6 +103,7 @@ interface Restriction {
        /**
         * Create a unique hash of the block restriction based on the type and value.
         *
+        * @since 1.33
         * @return string
         */
        public function getHash();
index a39568b..6a8bd6d 100644 (file)
@@ -58,8 +58,6 @@ class ChangesList extends ContextSource {
        protected $filterGroups;
 
        /**
-        * Changeslist constructor
-        *
         * @param Skin|IContextSource $obj
         * @param array $filterGroups Array of ChangesListFilterGroup objects (currently optional)
         */
index ad95bb4..23ff304 100644 (file)
@@ -406,16 +406,15 @@ class OldLocalFile extends LocalFile {
         * Upload a file directly into archive. Generally for Special:Import.
         *
         * @param string $srcPath File system path of the source file
-        * @param string $archiveName Full archive name of the file, in the form
-        *   $timestamp!$filename, where $filename must match $this->getName()
         * @param string $timestamp
         * @param string $comment
         * @param User $user
         * @return Status
         */
-       function uploadOld( $srcPath, $archiveName, $timestamp, $comment, $user ) {
+       public function uploadOld( $srcPath, $timestamp, $comment, $user ) {
                $this->lock();
 
+               $archiveName = $this->getArchiveName();
                $dstRel = $this->getArchiveRel( $archiveName );
                $status = $this->publishTo( $srcPath, $dstRel );
 
index 5d2019c..98cf3b1 100644 (file)
@@ -23,6 +23,8 @@ class HTMLSelectLanguageField extends HTMLSelectField {
                        $languages[$languageCode] = $languageCode;
                }
 
+               ksort( $languages );
+
                foreach ( $languages as $code => $name ) {
                        $this->mParams['options'][$code . ' - ' . $name] = $code;
                }
index 4fbddb5..4b378c1 100644 (file)
@@ -104,9 +104,13 @@ class ImportableUploadRevisionImporter implements UploadRevisionImporter {
                        ?: User::newFromName( $importableRevision->getUser(), false );
 
                # Do the actual upload
-               if ( $archiveName ) {
-                       $status = $file->uploadOld( $source, $archiveName,
-                               $importableRevision->getTimestamp(), $importableRevision->getComment(), $user );
+               if ( $file instanceof OldLocalFile ) {
+                       $status = $file->uploadOld(
+                               $source,
+                               $importableRevision->getTimestamp(),
+                               $importableRevision->getComment(),
+                               $user
+                       );
                } else {
                        $flags = 0;
                        $status = $file->upload(
index 6928530..d681fe3 100644 (file)
        "config-sqlite-dir-help": "SQLite-k datu guztiak fitxategi bakarrean gordetzen ditu.\n\nHornitu duzun direktorioa web zerbitzariaren bidez idatzia izateko aukera eman beharko duu instalazioan zehar.\n\n<Strong>Ez</strong> da webgunearen bidez eskuragarri egon behar; horregatik zure PHP fitxategiak non dauden ez dugu erakutsi.\n\nInstalatzaileak <code>.htaccess</code> fitxategi bat idatziko du bertan, baina horrek huts egiten badu zure datu base gordinera norbait sar daiteke.\nErabiltzaileen datu gordinak (helbide elektronikoak, pasahitzak), ezabatutako berrikusketa eta gainontzeko datu mugatuak ere barnean hartuz.\n\nDatu-basea beste nonbait jartzearen inguruan hausnartu, adibidez, <code>/var/lib/mediawiki/yourwiki</code>-n.",
        "config-oracle-def-ts": "Taula-toki lehenetsia:",
        "config-oracle-temp-ts": "Aldi baterako taula:",
-       "config-type-mysql": "MySQL (edo bateragarria)",
+       "config-type-mysql": "MariaDB, MySQL edo bateragarria",
        "config-type-postgres": "PostgreSQL",
        "config-type-sqlite": "SQLite",
        "config-type-oracle": "Oracle",
index 421279c..0f004c1 100644 (file)
@@ -1,7 +1,8 @@
 {
        "@metadata": {
                "authors": [
-                       "Giromin Cangiaxo"
+                       "Giromin Cangiaxo",
+                       "Fitoschido"
                ]
        },
        "config-desc": "Programma de installaçion pe MediaWiki",
        "config-sqlite-dir-help": "SQLite o memorizza tutti i dæti inte 'n unnico file.\n\nA directory che t'indichiæ a dev'ese scrivibile da-o serviou web durante l'instalaçion.\n\nA dev'ese <strong>non acescibbile via web</strong>, l'è pe questo che no a mettemmo donde gh'è i file PHP.\n\nL'instalou o ghe scriviâ insemme un file <code>.htaccess</code>, ma se o tentativo o falisse quarcun poriæ avei accesso a-o database sgroeuzzo.\nQuesto o l'includde di dæti utente sgroeuzzi (adressi, password çiffræ) coscì comme de vercsioin eliminæ e atri dæti a accesso limitou da wiki.\n\nConsciddera a-a dreitua l'oportunitæ d'alugâ o database da quarch'atra parte, prezempio in <code>/var/lib/mediawiki/tuowiki</code>.",
        "config-oracle-def-ts": "Tablespace pe difetto:",
        "config-oracle-temp-ts": "Tablespace tempoannio:",
-       "config-type-mysql": "MySQL (ò compatibbile)",
+       "config-type-mysql": "MariaDB, MySQL ò compatibbile",
        "config-type-mssql": "Microsoft SQL Server",
        "config-support-info": "MediaWiki o supporta i seguenti scistemi de database:\n\n$1\n\nSe fra quelli elencæ chì de sotta no ti veddi o scistema de database che ti voriesci doeuviâ, segui e instruçioin inganciæ de d'ato pe abilitâ o supporto.",
        "config-dbsupport-mysql": "* [{{int:version-db-mysql-url}} MySQL] a l'è a primma scelta pe MediaWiki e a l'è quella megio suportâ. MediaWiki a fonçion-a ascì con [{{int:version-db-mariadb-url}} MariaDB] e [{{int:version-db-percona-url}} Percona Server], che son compatibbili con MySQL.([https://secure.php.net/manual/en/mysqli.installation.php Comme compilâ PHP con suporto MySQL])",
index 7198522..e88aec2 100644 (file)
@@ -4,7 +4,8 @@
                        "Cedric31",
                        "Jfblanc",
                        "Seb35",
-                       "Nicolas Eynaud"
+                       "Nicolas Eynaud",
+                       "Fitoschido"
                ]
        },
        "config-desc": "Lo programa d’installacion de MediaWiki",
@@ -69,7 +70,7 @@
        "config-sqlite-dir": "Dorsièr de las donadas SQLite :",
        "config-oracle-def-ts": "Espaci d'emmagazinatge (''tablespace'') per defaut :",
        "config-oracle-temp-ts": "Espaci d'emmagazinatge (''tablespace'') temporari :",
-       "config-type-mysql": "MySQL (o compatible)",
+       "config-type-mysql": "MariaDB, MySQL o compatible",
        "config-type-mssql": "Microsoft SQL Server",
        "config-header-mysql": "Paramètres de MySQL",
        "config-header-postgres": "Paramètres de PostgreSQL",
index 048ac02..08679ae 100644 (file)
@@ -6,7 +6,8 @@
                        "Stelistcristi",
                        "XXN",
                        "Tuxilina",
-                       "Strainu"
+                       "Strainu",
+                       "Fitoschido"
                ]
        },
        "config-desc": "Programul de instalare pentru MediaWiki",
@@ -71,7 +72,7 @@
        "config-sqlite-dir": "Director de date SQLite:",
        "config-oracle-def-ts": "Spațiu de stocare („tablespace”) implicit:",
        "config-oracle-temp-ts": "Spațiu de stocare („tablespace”) temporar:",
-       "config-type-mysql": "MySQL (sau compatibil)",
+       "config-type-mysql": "MariaDB, MySQL sau compatibil",
        "config-type-mssql": "Microsoft SQL Server",
        "config-header-mysql": "Setările MySQL",
        "config-header-postgres": "Setări PostgreSQL",
index 51a3ba4..76e37aa 100644 (file)
        "config-install-mainpage-failed": "Не вдається вставити головну сторінку: $1",
        "config-install-done": "<strong>Вітаємо!</strong>\nВи успішно встановили MediaWiki.\n\nІнсталятор згенерував файл <code>LocalSettings.php</code>, який містить усі Ваші налаштування.\n\nВам необхідно завантажити його і помістити у кореневу папку Вашої вікі (туди ж, де index.php). Завантаження мало початись автоматично.\n\nЯкщо завантаження не почалось або Ви його скасували, можете заново його почати, натиснувши на посилання внизу:\n\n$3\n\n<strong>Примітка</strong>: Якщо Ви не зробите цього зараз, цей файл не буде доступним пізніше, коли Ви вийдете з встановлення, не скачавши його.\n\nПісля виконання дій, описаних вище, Ви зможете <strong>[$2 увійти у свою вікі]</strong>.",
        "config-install-done-path": "<strong>Вітаємо!</strong>\nВи встановили Медіавікі.\n\nІнсталятор створив файл <code>LocalSettings.php</code>.\nУ ньому містяться всі Ваші налаштування.\n\nВам потрібно завантажити його й помістити в <code>$4</code>. Завантаження повинно було автоматично розпочатись.\n\nЯкщо завантаження не було запропоновано, або Ви його скасували, Ви можете перезапустити завантаження натиснувши на посилання нижче:\n\n$3\n\n<strong>Зверніть увагу:</strong> Якщо Ви не зробите це зараз, цей згенерований файл налаштувань не буде доступним для Вас пізніше якщо Ви вийдете зі встановлення не завантаживши його.\n\nКоли це було зроблено Ви можете <strong>[$2 зайти до своєї вікі]</strong>.",
-       "config-install-success": "Mediawiki Ñ\83Ñ\81пÑ\96Ñ\88но Ð²Ñ\81Ñ\82ановлено. Ð\97аÑ\80аз Ð²Ð¸ Ð¼Ð¾Ð¶ÐµÑ\82е Ð¿ÐµÑ\80ейÑ\82и Ð´Ð¾ <$1$2>, Ñ\89об Ð¿ÐµÑ\80еглÑ\8fнÑ\83Ñ\82и Ñ\81воÑ\8e Ð²Ñ\96кÑ\96. Ð¯ÐºÑ\89о Ñ\83 Ð²Ð°Ñ\81 Ñ\94 Ð¿Ð¸Ñ\82аннÑ\8f, Ð¾Ð·Ð½Ð°Ð¹Ð¾Ð¼Ñ\82еÑ\81Ñ\8f Ð· Ð½Ð°Ñ\88им FAQ: <https://www.mediawiki.org/wiki/Manual:FAQ> або використовуйте один з форумів підтримки, які вказано на цій сторінці.",
+       "config-install-success": "Mediawiki Ñ\83Ñ\81пÑ\96Ñ\88но Ð²Ñ\81Ñ\82ановлено. Ð\97аÑ\80аз Ð\92и Ð¼Ð¾Ð¶ÐµÑ\82е Ð¿ÐµÑ\80ейÑ\82и Ð´Ð¾ <$1$2>, Ñ\89об Ð¿ÐµÑ\80еглÑ\8fнÑ\83Ñ\82и Ñ\81воÑ\8e Ð²Ñ\96кÑ\96. Ð¯ÐºÑ\89о Ñ\83 Ð\92аÑ\81 Ñ\94 Ð¿Ð¸Ñ\82аннÑ\8f, Ð¾Ð·Ð½Ð°Ð¹Ð¾Ð¼Ñ\82еÑ\81Ñ\8f Ð· Ð½Ð°Ñ\88им FAQ: <https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ> або використовуйте один з форумів підтримки, які вказано на цій сторінці.",
        "config-download-localsettings": "Завантажити <code>LocalSettings.php</code>",
        "config-help": "допомога",
        "config-help-tooltip": "натисніть, щоб розгорнути",
        "config-nofile": "Файл \"$1\" не знайдено. Його видалено?",
-       "config-extension-link": "Чи знаєте ви, що ваше вікі підтримує [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions розширення]?\n\nВи можете переглядати [https://www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category розширення по категорії] або в [https://www.mediawiki.org/wiki/Extension_Matrix матрицю розширень] щоб побачити повний список розширень.",
+       "config-extension-link": "Чи знали Ви, що Ваша вікі підтримує [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Extensions розширення]?\n\nМожете переглянути [https://www.mediawiki.org/wiki/Special:MyLanguage/Category:Extensions_by_category розширення за категорією]",
        "config-skins-screenshots": "$1 (скріншоти: $2)",
        "config-extensions-requires": "$1 (вимагає $2)",
        "config-screenshot": "скріншот",
+       "config-extension-not-found": "Не вдалося знайти файл реєстрації для розширення «$1»",
+       "config-extension-dependency": "При встановленні розширення «$1» виникла помилка залежності: $2",
        "mainpagetext": "<strong>Програмне забезпечення «MediaWiki» встановлено.</strong>",
        "mainpagedocfooter": "Інформацію про роботу з цією вікі можна знайти на сторінці [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents Довідка:Вміст].\n\n== Деякі корисні ресурси ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Список налаштувань];\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ Часті питання з приводу MediaWiki];\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-announce Розсилка повідомлень про появу нових версій MediaWiki];\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Локалізувати MediaWiki своєю мовою]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Дізнатися, як боротися зі спамом у своїй вікі]"
 }
index 802ed2d..4ae3cf9 100644 (file)
@@ -311,7 +311,6 @@ class IEContentAnalyzer {
         */
        protected $typeTable = [];
 
-       /** constructor */
        function __construct() {
                // Construct versioned type arrays from the base type array plus additions
                $types = $this->baseTypeTable;
index 4bbebd6..6e8d266 100644 (file)
@@ -941,6 +941,36 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         *     );
         * @endcode
         *
+        * Example usage (key that is expensive with too many DB dependencies for "check keys"):
+        * @code
+        *     $catToys = $cache->getWithSetCallback(
+        *         // Key to store the cached value under
+        *         $cache->makeKey( 'cat-toys', $catId ),
+        *         // Time-to-live (seconds)
+        *         $cache::TTL_HOUR,
+        *         // Function that derives the new key value
+        *         function ( $oldValue, &$ttl, array &$setOpts ) {
+        *             // Determine new value from the DB
+        *             $dbr = wfGetDB( DB_REPLICA );
+        *             // Account for any snapshot/replica DB lag
+        *             $setOpts += Database::getCacheSetOptions( $dbr );
+        *
+        *             return CatToys::newFromResults( $dbr->select( ... ) );
+        *         },
+        *         [
+        *              // Get the highest timestamp of any of the cat's toys
+        *             'touchedCallback' => function ( $value ) use ( $catId ) {
+        *                 $dbr = wfGetDB( DB_REPLICA );
+        *                 $ts = $dbr->selectField( 'cat_toys', 'MAX(ct_touched)', ... );
+        *
+        *                 return wfTimestampOrNull( TS_UNIX, $ts );
+        *             },
+        *             // Avoid DB queries for repeated access
+        *             'pcTTL' => $cache::TTL_PROC_SHORT
+        *         ]
+        *     );
+        * @endcode
+        *
         * Example usage (hot key holding most recent 100 events):
         * @code
         *     $lastCatActions = $cache->getWithSetCallback(
@@ -1082,8 +1112,8 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
         *      expired for this specified time. This is useful if adaptiveTTL() is used on the old
         *      value's as-of time when it is verified as still being correct.
         *      Default: WANObjectCache::STALE_TTL_NONE
-        *   - touchedCallback: A callback that takes the current value and returns a timestamp that
-        *      indicates the last time a dynamic dependency changed. Null can be returned if there
+        *   - touchedCallback: A callback that takes the current value and returns a UNIX timestamp
+        *      indicating the last time a dynamic dependency changed. Null can be returned if there
         *      are no relevant dependency changes to check. This can be used to check against things
         *      like last-modified times of files or DB timestamp fields. This should generally not be
         *      used for small and easily queried values in a DB if the callback itself ends up doing
index c7c7069..b1bf7bd 100644 (file)
@@ -2147,6 +2147,7 @@ class WikiPage implements Page, IDBAccessObject {
                }
 
                $this->loadPageData( 'fromdbmaster' );
+               $this->mTitle->loadRestrictions( null, Title::READ_LATEST );
                $restrictionTypes = $this->mTitle->getRestrictionTypes();
                $id = $this->getId();
 
index 0c52354..9eb921d 100644 (file)
@@ -43,7 +43,7 @@ class UserPasswordPolicy {
        /**
         * @param array $policies
         * @param array $checks mapping statement to its checking function. Checking functions are
-        * called with the policy value for this user, the user object, and the password to check.
+        *   called with the policy value for this user, the user object, and the password to check.
         */
        public function __construct( array $policies, array $checks ) {
                if ( !isset( $policies['default'] ) ) {
@@ -68,7 +68,9 @@ class UserPasswordPolicy {
         * @param User $user who's policy we are checking
         * @param string $password the password to check
         * @return Status error to indicate the password didn't meet the policy, or fatal to
-        *      indicate the user shouldn't be allowed to login.
+        *   indicate the user shouldn't be allowed to login. The status value will be an array,
+        *   potentially with the following keys:
+        *   - forceChange: do not allow the user to login without changing the password if invalid.
         */
        public function checkUserPassword( User $user, $password ) {
                $effectivePolicy = $this->getPoliciesForUser( $user );
@@ -88,7 +90,9 @@ class UserPasswordPolicy {
         * @param string $password the password to check
         * @param array $groups list of groups to which we assume the user belongs
         * @return Status error to indicate the password didn't meet the policy, or fatal to
-        *      indicate the user shouldn't be allowed to login.
+        *   indicate the user shouldn't be allowed to login. The status value will be an array,
+        *   potentially with the following keys:
+        *   - forceChange: do not allow the user to login without changing the password if invalid.
         */
        public function checkUserPasswordForGroups( User $user, $password, array $groups ) {
                $effectivePolicy = self::getPoliciesForGroups(
@@ -112,19 +116,34 @@ class UserPasswordPolicy {
         * @return Status
         */
        private function checkPolicies( User $user, $password, $policies, $policyCheckFunctions ) {
-               $status = Status::newGood();
-               foreach ( $policies as $policy => $value ) {
+               $status = Status::newGood( [] );
+               $forceChange = false;
+               foreach ( $policies as $policy => $settings ) {
                        if ( !isset( $policyCheckFunctions[$policy] ) ) {
                                throw new DomainException( "Invalid password policy config. No check defined for '$policy'." );
                        }
-                       $status->merge(
-                               call_user_func(
-                                       $policyCheckFunctions[$policy],
-                                       $value,
-                                       $user,
-                                       $password
-                               )
+                       if ( !is_array( $settings ) ) {
+                               // legacy format
+                               $settings = [ 'value' => $settings ];
+                       }
+                       if ( !array_key_exists( 'value', $settings ) ) {
+                               throw new DomainException( "Invalid password policy config. No value defined for '$policy'." );
+                       }
+                       $value = $settings['value'];
+                       /** @var StatusValue $policyStatus */
+                       $policyStatus = call_user_func(
+                               $policyCheckFunctions[$policy],
+                               $value,
+                               $user,
+                               $password
                        );
+                       if ( !$policyStatus->isGood() && !empty( $settings['forceChange'] ) ) {
+                               $forceChange = true;
+                       }
+                       $status->merge( $policyStatus );
+               }
+               if ( $status->isOK() && $forceChange ) {
+                       $status->value['forceChange'] = true;
                }
                return $status;
        }
@@ -174,6 +193,7 @@ class UserPasswordPolicy {
        /**
         * Utility function to get a policy that is the most restrictive of $p1 and $p2. For
         * simplicity, we setup the policy values so the maximum value is always more restrictive.
+        * It is also used recursively to merge settings within the same policy.
         * @param array $p1
         * @param array $p2
         * @return array containing the more restrictive values of $p1 and $p2
@@ -186,8 +206,15 @@ class UserPasswordPolicy {
                                $ret[$key] = $p2[$key];
                        } elseif ( !isset( $p2[$key] ) ) {
                                $ret[$key] = $p1[$key];
-                       } else {
+                       } elseif ( !is_array( $p1[$key] ) && !is_array( $p2[$key] ) ) {
                                $ret[$key] = max( $p1[$key], $p2[$key] );
+                       } else {
+                               if ( !is_array( $p1[$key] ) ) {
+                                       $p1[$key] = [ 'value' => $p1[$key] ];
+                               } elseif ( !is_array( $p2[$key] ) ) {
+                                       $p2[$key] = [ 'value' => $p2[$key] ];
+                               }
+                               $ret[$key] = self::maxOfPolicies( $p1[$key], $p2[$key] );
                        }
                }
                return $ret;
index 6a21e85..47d61b8 100644 (file)
@@ -156,22 +156,31 @@ class SpecialBlock extends FormSpecialPage {
                        'type' => 'user',
                        'ipallowed' => true,
                        'iprange' => true,
-                       'label-message' => 'ipaddressorusername',
                        'id' => 'mw-bi-target',
                        'size' => '45',
                        'autofocus' => true,
                        'required' => true,
                        'validation-callback' => [ __CLASS__, 'validateTargetField' ],
+                       'section' => 'target',
+               ];
+
+               $a['Editing'] = [
+                       'type' => 'check',
+                       'label-message' => 'block-prevent-edit',
+                       'default' => true,
+                       'section' => 'actions',
+                       'disabled' => $enablePartialBlocks ? false : true,
                ];
 
                if ( $enablePartialBlocks ) {
                        $a['EditingRestriction'] = [
                                'type' => 'radio',
-                               'label' => $this->msg( 'ipb-type-label' )->text(),
+                               'cssclass' => 'mw-block-editing-restriction',
                                'options' => [
                                        $this->msg( 'ipb-sitewide' )->text() => 'sitewide',
                                        $this->msg( 'ipb-partial' )->text() => 'partial',
                                ],
+                               'section' => 'actions',
                        ];
                        $a['PageRestrictions'] = [
                                'type' => 'titlesmultiselect',
@@ -183,38 +192,22 @@ class SpecialBlock extends FormSpecialPage {
                                'input' => [
                                        'autocomplete' => false
                                ],
+                               'section' => 'actions',
                        ];
                }
 
-               $a['Expiry'] = [
-                       'type' => 'expiry',
-                       'label-message' => 'ipbexpiry',
-                       'required' => true,
-                       'options' => $suggestedDurations,
-                       'default' => $this->msg( 'ipb-default-expiry' )->inContentLanguage()->text(),
-               ];
-
-               $a['Reason'] = [
-                       'type' => 'selectandother',
-                       // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
-                       // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
-                       // Unicode codepoints (or 255 UTF-8 bytes for old schema).
-                       'maxlength' => $oldCommentSchema ? 255 : CommentStore::COMMENT_CHARACTER_LIMIT,
-                       'maxlength-unit' => 'codepoints',
-                       'label-message' => 'ipbreason',
-                       'options-message' => 'ipbreason-dropdown',
-               ];
-
                $a['CreateAccount'] = [
                        'type' => 'check',
                        'label-message' => 'ipbcreateaccount',
                        'default' => true,
+                       'section' => 'actions',
                ];
 
                if ( self::canBlockEmail( $user ) ) {
                        $a['DisableEmail'] = [
                                'type' => 'check',
                                'label-message' => 'ipbemailban',
+                               'section' => 'actions',
                        ];
                }
 
@@ -223,13 +216,34 @@ class SpecialBlock extends FormSpecialPage {
                                'type' => 'check',
                                'label-message' => 'ipb-disableusertalk',
                                'default' => false,
+                               'section' => 'actions',
                        ];
                }
 
+               $a['Expiry'] = [
+                       'type' => 'expiry',
+                       'required' => true,
+                       'options' => $suggestedDurations,
+                       'default' => $this->msg( 'ipb-default-expiry' )->inContentLanguage()->text(),
+                       'section' => 'expiry',
+               ];
+
+               $a['Reason'] = [
+                       'type' => 'selectandother',
+                       // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
+                       // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
+                       // Unicode codepoints (or 255 UTF-8 bytes for old schema).
+                       'maxlength' => $oldCommentSchema ? 255 : CommentStore::COMMENT_CHARACTER_LIMIT,
+                       'maxlength-unit' => 'codepoints',
+                       'options-message' => 'ipbreason-dropdown',
+                       'section' => 'reason',
+               ];
+
                $a['AutoBlock'] = [
                        'type' => 'check',
                        'label-message' => 'ipbenableautoblock',
                        'default' => true,
+                       'section' => 'options',
                ];
 
                # Allow some users to hide name from block log, blocklist and listusers
@@ -238,6 +252,7 @@ class SpecialBlock extends FormSpecialPage {
                                'type' => 'check',
                                'label-message' => 'ipbhidename',
                                'cssclass' => 'mw-block-hideuser',
+                               'section' => 'options',
                        ];
                }
 
@@ -246,6 +261,7 @@ class SpecialBlock extends FormSpecialPage {
                        $a['Watch'] = [
                                'type' => 'check',
                                'label-message' => 'ipbwatchuser',
+                               'section' => 'options',
                        ];
                }
 
@@ -253,6 +269,7 @@ class SpecialBlock extends FormSpecialPage {
                        'type' => 'check',
                        'label-message' => 'ipb-hardblock',
                        'default' => false,
+                       'section' => 'options',
                ];
 
                # This is basically a copy of the Target field, but the user can't change it, so we
index 0f9ded3..3c2907b 100644 (file)
@@ -170,11 +170,12 @@ class UserrightsPage extends SpecialPage {
                                $targetUser->clearInstanceCache(); // T40989
                        }
 
-                       $checkValue = explode( ',', $request->getVal( 'conflictcheck-originalgroups' ) );
+                       $conflictCheck = $request->getVal( 'conflictcheck-originalgroups' );
+                       $conflictCheck = ( $conflictCheck === '' ) ? [] : explode( ',', $conflictCheck );
                        $userGroups = $targetUser->getGroups();
 
-                       if ( $userGroups !== $checkValue ) {
-                               $out->addWikiMsg( 'userrights-conflict' );
+                       if ( $userGroups !== $conflictCheck ) {
+                               $out->wrapWikiMsg( '<span class="error">$1</span>', 'userrights-conflict' );
                        } else {
                                $status = $this->saveUserGroups(
                                        $this->mTarget,
index c7dbf83..a579b69 100644 (file)
@@ -1740,9 +1740,10 @@ abstract class UploadBase {
                        }
 
                        # image filters can pull in url, which could be svg that executes scripts
+                       # Only allow url( "#foo" ). Do not allow url( http://example.com )
                        if ( $strippedElement == 'image'
                                && $stripped == 'filter'
-                               && preg_match( '!url\s*\(!sim', $value )
+                               && preg_match( '!url\s*\(\s*["\']?[^#]!sim', $value )
                        ) {
                                wfDebug( __METHOD__ . ": Found image filter with url: "
                                        . "\"<$strippedElement $stripped='$value'...\" in uploaded file.\n" );
index 22fe44c..65fc4b4 100644 (file)
@@ -1179,15 +1179,17 @@ class User implements IDBAccessObject, UserIdentity {
        /**
         * Check if this is a valid password for this user
         *
-        * Create a Status object based on the password's validity.
-        * The Status should be set to fatal if the user should not
-        * be allowed to log in, and should have any errors that
-        * would block changing the password.
-        *
-        * If the return value of this is not OK, the password
-        * should not be checked. If the return value is not Good,
-        * the password can be checked, but the user should not be
-        * able to set their password to this.
+        * Returns a Status object with a set of messages describing
+        * problems with the password. If the return status is fatal,
+        * the action should be refused and the password should not be
+        * checked at all (this is mainly meant for DoS mitigation).
+        * If the return value is OK but not good, the password can be checked,
+        * but the user should not be able to set their password to this.
+        * The value of the returned Status object will be an array which
+        * can have the following fields:
+        * - forceChange (bool): if set to true, the user should not be
+        *   allowed to log with this password unless they change it during
+        *   the login process (see ResetPasswordSecondaryAuthenticationProvider).
         *
         * @param string $password Desired password
         * @return Status
@@ -1201,7 +1203,7 @@ class User implements IDBAccessObject, UserIdentity {
                        $wgPasswordPolicy['checks']
                );
 
-               $status = Status::newGood();
+               $status = Status::newGood( [] );
                $result = false; // init $result to false for the internal checks
 
                if ( !Hooks::run( 'isValidPassword', [ $password, &$result, $this ] ) ) {
@@ -1210,7 +1212,7 @@ class User implements IDBAccessObject, UserIdentity {
                }
 
                if ( $result === false ) {
-                       $status->merge( $upp->checkUserPassword( $this, $password ) );
+                       $status->merge( $upp->checkUserPassword( $this, $password ), true );
                        return $status;
                } elseif ( $result === true ) {
                        return $status;
@@ -4543,6 +4545,7 @@ class User implements IDBAccessObject, UserIdentity {
        /**
         * Get whether the user is blocked from using Special:Upload
         *
+        * @since 1.33
         * @return bool
         */
        public function isBlockedFromUpload() {
index 46f3bbc..46f1aaa 100644 (file)
@@ -127,7 +127,6 @@ class ZipDirectoryReader {
        const GENERAL_CD_ENCRYPTED = 13;
 
        /**
-        * Private constructor
         * @param string $fileName
         * @param callable $callback
         * @param array $options
index 797a81f..06d8095 100644 (file)
@@ -19,8 +19,6 @@ class CheckMatrixWidget extends \OOUI\Widget {
        protected $forcedOff = [];
 
        /**
-        * CheckMatrixWidget constructor
-        *
         * Operates similarly to MultiSelectWidget, but instead of using an array of
         * options, uses an array of rows and an array of columns to dynamically
         * construct a matrix of options. The tags used to identify a particular cell
index ffdc4c4..fac054e 100644 (file)
        "statistics": "Статыстыка",
        "statistics-header-pages": "Статыстыка старонак",
        "statistics-header-edits": "Статыстыка рэдагаваньняў",
-       "statistics-header-users": "СÑ\82аÑ\82Ñ\8bÑ\81Ñ\82Ñ\8bка Ñ\9eдзелÑ\83",
+       "statistics-header-users": "СÑ\82аÑ\82Ñ\8bÑ\81Ñ\82Ñ\8bка Ñ\9eдзелÑ\8cнÑ\96каÑ\9e",
        "statistics-header-hooks": "Іншая статыстыка",
        "statistics-articles": "Колькасьць старонак са зьместам",
        "statistics-pages": "Колькасьць старонак",
index ab446f2..17624ac 100644 (file)
        "logentry-delete-delete": "$1 {{GENDER:$2|выдаліў|выдаліла}} старонку $3",
        "logentry-delete-delete_redir": "$1 {{GENDER:$2|выдаліў|выдаліла}} перанакіраванне $3 шляхам перазапісу",
        "logentry-delete-restore": "$1 {{GENDER:$2|аднавіў|аднавіла}} старонку $3 ($4)",
+       "logentry-delete-restore-nocount": "$1 {{GENDER:$2|аднавіў|аднавіла}} старонку $3",
        "restore-count-revisions": "{{PLURAL:$1|1 версія|$1 версіі|$1 версій}}",
        "logentry-delete-event": "$1 {{GENDER:$2|змяніў|змяніла}} бачнасць {{PLURAL:$5|запісу журнала|$5 запісаў журнала}} $3: $4",
        "logentry-delete-revision": "$1 {{GENDER:$2|змяніў|змяніла}} бачнасць {{PLURAL:$5|версіі|$5 версій|$5 версій}} старонкі $3: $4",
index 717a2ab..36239fe 100644 (file)
@@ -15,6 +15,9 @@
        },
        "tog-underline": "هومپیڤٱندا زیر خٱتدار",
        "tog-hideminor": "دٱم تی نٱبیڌن آلشتا کۊچیر",
+       "tog-hidepatrolled": "بؽدیار نڤیڌن آلشڌٱل کوچیر",
+       "tog-newpageshidepatrolled": "بٱلٛگیٱل لرهٱرڌاْ زاْ فاٛئرست بٱلٛگیٱل نۊ بؽدیار ڤۊهاْ",
+       "tog-hidecategorization": "بؽدیارنیڌن رٱئڌاٛڤٱنی بٱلٛگیٱل",
        "tog-extendwatchlist": "گپ کردن نوم گه آ مو سی دیئن همه آلشتا نه فقط هونو که بیشتر ز همه انجوم ابون.",
        "tog-usenewrc": "جٱرغاٛ کاری آلشتا ڤا آلشتکاری بٱلگاٛیلسۊن و سئیل بٱرگسۊن",
        "tog-numberheadings": "شوماراٛ ڤٱندن خودٱنجوم سی سربٱلگاٛیل",
        "tog-watchdefault": "اٛزاف کردن بٱلگاٛیٱل و جانیایٱلی کاٛ مو مئن سئیل برگوم ڤیرایشدسۊن کردوماٛ",
        "tog-watchmoves": "اْزاف کردن بٱلگاٛیٱلی کاٛ خوم جا ب جاسۊن کردوماٛ سی سئل بٱرگوم",
        "tog-watchdeletion": "اٛزاف کردن بٱلگاٛیٱل و جانیایٱلی کاٛ خوم ز مئن سئیل بٱرگوم پاکسا کردوماٛ",
-       "tog-minordefault": "علامت نهادن به اصلاحات ناقص",
-       "tog-previewontop": "نشودادن پیش نمایش قبل از یوکه جعبه یا کادر اصلاح بوه",
-       "tog-previewonfirst": "نشو دادن پیش نمایش  دراصلاح اول",
-       "tog-enotifwatchlistpages": "امیل به مو وقتی که  صفحه ای که منه فهرست نمایش مونه تغییر کرد",
+       "tog-watchuploads": "پٱرڤٱنداٛیٱل نۊئی کاْ باراْنم ڤاْ فاٛئرسڌ دیناگریٱل مو بالاڤٱن ڤۊ",
+       "tog-watchrollback": "بالاڤٱن کرڌن بٱلٛگیٱلؽ کاْ اْؤورگٱرنیم ڤاْ فاٛئرسڌ دیناگریٱل مو",
+       "tog-minordefault": "دیاری کردن جۊر ڤیرایشتا ناقس",
+       "tog-previewontop": "دیاری کردن پیش سئیل پیش ز ڤیرایشد جٱڤٱ",
+       "tog-previewonfirst": "دیاری کردن پیش سئیل مئن ٱڤلین ڤیرایشد",
+       "tog-enotifwatchlistpages": "هر گاتی کاٛ یٱ بٱلگٱ یا یٱ جانیا مئن سئیل بٱرگ مۊ آلشد ابۊ بوم خٱڤٱر بڌین",
        "tog-enotifusertalkpages": "امیل به مو وقتی که صفحه گفتگوی مو تغییر کرد",
        "tog-enotifminoredits": "او بٱلگاٛیٱل و جانیایٱلی کاٛ ڤیرایشد کۊچیر و ناقس دارن بفرشن سی ٱنجوماناموم",
        "tog-enotifrevealaddr": "دیاری کردن تیرنشۊن ٱنجوماناماٛ مو مئن دیارکاری ایمیلی",
        "tog-shownumberswatching": "نشودادن شماره کاربران درحال کار یاتماشا",
        "tog-oldsig": "اٛمزا ایسئنی",
-       "tog-fancysig": "اÙ\85ضاÛ\8cÙ\84 Ù\86اتموم",
+       "tog-fancysig": "اÙ\9bÙ\85زاÛ\8cÙ±Ù\84 Ù\86اتٱموم",
        "tog-uselivepreview": "پیش سئیل زندٱ نٱ ڤٱن ڤا کار",
        "tog-forceeditsummary": "یادآوری سریع به مو هنگام اصلاح عقیم وخلاصه",
        "tog-watchlisthideown": "قایم کردن اصلاحات مو  زه لیست پیگیریها",
        "tog-watchlisthidebots": "قایم کردن اصلاحات بوت زه لیست پیگیریها",
        "tog-watchlisthideminor": "قایم کردن اصلاحات ریز زه لیست پیگیریها",
        "tog-watchlisthideliu": "قایم کردن اصلاحات انجام وابیده  بوسیله کاربران داخل سیستم وابیده زه لیست پیگیریها",
+       "tog-watchlistreloadautomatically": "راتؽ کاْ یٱ پاْلایاٛ آلشڌآڤیڌ فاٛئرسڌ دیناگری بؽنگوڌ(خوڌکار)ڤ رۊز ڤۊهاْ(هوجاْ ڤاْ جاڤا اسکریپت)",
+       "tog-watchlistunwatchlinks": "فرٱنیڌن دیاری کونٱنڌیٱل نڤیڌ دیناگری/دیناگری ({{int:Watchlist-unwatch}}/{{int:Watchlist-unwatch-undo}})ڤاْ بٱلٛگیٱل دیناگری آلشڌدار (سی عملیات کلؽز ۉ بلؽز کرڌن، جاڤاسکریپت   هوجاْ هؽڌآ)",
        "tog-watchlisthideanons": "قایم کردن اصلاحات انجام شده بوسیله کاربران داخل سیستم نشده زه لیست پیگیری",
+       "tog-watchlisthidepatrolled": "تٱپنیڌن پٱخڌارٱل ساوا زاْ لیسڌ دیناگریٱل\nتٱپنیڌن پٱخڌارٱل بۊت زاْ لیسڌ دیناگریٱل\nتٱپنیڌن پٱخڌارٱل مو زاْ لیسڌ دیناگریٱل",
+       "tog-watchlisthidecategorization": "تٱپنیڌن رٱئڌڤٱنی بٱلٛگیٱل",
        "tog-ccmeonemails": "ارسال کپی امیلهایی که مو به  کاربران دیه ارسال کردم به مو",
-       "tog-diffonly": "نشو نده صفحه ای که دارای محتوای متفاوت زیره",
-       "tog-showhiddencats": "نشودادن دسته بندیهای قایم شده",
-       "underline-always": "Ù\87Ù\85Û\8cØ´Ù\87",
-       "underline-never": "هرگز",
-       "underline-default": "Ù¾Û\88سدإ Ø¢ Ø¯Ù\88ڤارتإ Ù\86Ù\8aأر Ù¾Ù\8aØ´ Ù\81رز",
+       "tog-diffonly": "بٱلگاٛیی نٱ کاٛ مئنۊناٛ فٱرخداراٛ نشۊن مٱڌاٛ",
+       "tog-showhiddencats": "دیاری کردن جٱرغاٛ بٱندیٱل نادیار",
+       "underline-always": "Ù\87Ù\85Û\8cشٱ",
+       "underline-never": "هیژگات",
+       "underline-default": "Ù¾Û\8aسداÙ\9b Û\8cا Ø¯Ú¤Ù\88ارتاÙ\9b Ù\86Û\8cٱر Ù\85Û\8cزÛ\8aÙ\86کارÛ\8c Ú¤Ø§Ø¨Û\8cÚ\8cاÙ\9b",
        "editfont-sansserif": "فونت سان سئریف",
        "editfont-serif": "فونت سريف",
-       "sunday": "یکشنبه",
-       "monday": "دوشنبه",
-       "tuesday": "سه‌شنبه",
-       "wednesday": "چهارشنبه",
-       "thursday": "Ù¾Ù\86جشÙ\86بÙ\87",
-       "friday": "جÙ\85عÙ\87",
-       "saturday": "Ø´Ù\86بÙ\87",
-       "sun": "یکشنبه",
-       "mon": "دÙ\88Ø´Ù\86بÙ\87",
-       "tue": "سه‌شنبه",
-       "wed": "چهارشنبه",
-       "thu": "Ù¾Ù\86جشÙ\86بÙ\87",
-       "fri": "جÙ\85عÙ\87",
-       "sat": "Ø´Ù\86بÙ\87",
-       "january": "جانڤیە",
+       "sunday": "یٱشٱمبڌ",
+       "monday": "دۊشٱمبڌ",
+       "tuesday": "ساٛشٱمبڌ",
+       "wednesday": "چارشٱمبڌ",
+       "thursday": "پٱÙ\86شٱÙ\85بÚ\8c",
+       "friday": "جÙ\88Ù\85Ù±",
+       "saturday": "شٱÙ\85بÚ\8c",
+       "sun": "یٱشٱمبڌ",
+       "mon": "دÙ\88شٱÙ\85بÚ\8c",
+       "tue": "ساٛشٱمبڌ",
+       "wed": "چارشٱمبڌ",
+       "thu": "پٱÙ\86شٱÙ\85بÚ\8c",
+       "fri": "جÙ\88Ù\85Ù±",
+       "sat": "شٱÙ\85بÚ\8c",
+       "january": "جانڤیٱ",
        "february": "فڤریٱ",
        "march": "مارس",
        "april": "آڤریل",
-       "may_long": "مە",
-       "june": "جوٙأن",
-       "july": "جوٙئیە",
+       "may_long": "مئی",
+       "june": "جۊٱن",
+       "july": "جۊئیٱ",
        "august": "آگوست",
-       "september": "سئپتامر",
-       "october": "ئوکتوبر",
+       "september": "سپتامر",
+       "october": "اوکتوبر",
        "november": "نوڤامر",
-       "december": "دئساÙ\85ر",
-       "january-gen": "ژانویه",
-       "february-gen": "فوریه",
+       "december": "دسامر",
+       "january-gen": "جانڤیٱ",
+       "february-gen": "فڤریٱ",
        "march-gen": "مارس",
-       "april-gen": "آوریل",
-       "may-gen": "مه",
-       "june-gen": "ژوئن",
-       "july-gen": "ژوئیه",
-       "august-gen": "اÙ\88ت",
+       "april-gen": "آڤریل",
+       "may-gen": "مئی",
+       "june-gen": "جۊٱن",
+       "july-gen": "جۊئیٱ",
+       "august-gen": "Ø¢Ú¯Ù\88ست",
        "september-gen": "سپتامبر",
-       "october-gen": "اکتبر",
-       "november-gen": "نوامبر",
-       "december-gen": "دساÙ\85بر",
-       "jan": "جانڤیە",
-       "feb": "فئڤریە",
+       "october-gen": "اوکتوبر",
+       "november-gen": "نوڤامر",
+       "december-gen": "دسامر",
+       "jan": "جانڤیٱ",
+       "feb": "فڤریٱ",
        "mar": "مارس",
        "apr": "آڤریل",
-       "may": "مە",
-       "jun": "جوٙأن",
-       "jul": "جوٙئیە",
+       "may": "مئی",
+       "jun": "جۊٱن",
+       "jul": "جۊلای",
        "aug": "آگوست",
-       "sep": "سئپتامر",
-       "oct": "ئوکتوبر",
+       "sep": "سپتامر",
+       "oct": "اوکتوبر",
        "nov": "نوڤامر",
-       "dec": "دئساÙ\85ر",
-       "january-date": "جانڤيأ $1",
-       "february-date": "فإڤريأ $1",
+       "dec": "دسامر",
+       "january-date": "جانڤيٱ $1",
+       "february-date": "فڤریٱ $1",
        "march-date": "مارس  $1",
        "april-date": "آڤريل $1",
-       "may-date": "Ù\85Ø¥ی $1",
-       "june-date": "جÛ\88Ø£ن $1",
+       "may-date": "Ù\85ئی $1",
+       "june-date": "جÛ\8aÙ±ن $1",
        "july-date": "جۈلای $1",
        "august-date": "آگوست $1",
        "september-date": "سپتامر $1",
        "category-file-count": "{{PLURAL:$2|ای دسته فقط فایلهای زیر راداره.|ذیل الذکر{{PLURAL:$1|فایل است|$1 فایلهاهستند}} درای دسته, بیشترزه$2 کل.}}",
        "category-file-count-limited": "ذیل الذکر {{PLURAL:$1|فایل است|$1 فایلها هستند}} درآن دسته جریانی.",
        "listingcontinuesabbrev": "دنباله",
+       "noindex-category": "بٱلٛگاْیٱل نمایاْ ناڤیڌاْ",
        "about": "درباره",
        "article": "بلگه آ مینونه دار",
        "newwindow": "(پنجره تازه واز کن)",
        "yourtext": "متن ايسا",
        "copyrightwarning": "لطفاً دقت کنین که درنظر گریده ابوه که همه شراکتهای ایسا  {{SITENAME}} تحت «$2» منتشر ابون ).\n\n\n(سی دیدن  جزئیات بیشتر به $1 برین\n\nایر نه خوین نوشته‌هاتو بی‌رحمانه اصلاح بوه و به دلخواه ارسال بوه، ایچو نفرستن.<br />\nدرضمن ایسادارین به ایما قول ادین که خودتو یونه نوشتین یا هونه زه یک منبع آزاد با مالکیت عمومی یا مثل هو ورداشتین. '''کارهای دارای کارهای دارای حق کپی رایت را بی‌اجازه نفرستین!'''',",
        "templatesused": "{{PLURAL:$1|چوٙأ|چوٙأیل}} ب کار گرهڌأ ڤابيڌإ مإن اي بألگأ:",
-       "templatesusedpreview": "قالبها  یا الگوهای استفاده وابیده در ای پیش نمایش:",
+       "templatesusedpreview": "قالڤٱل یا اولگۊیٱل ڤاْ کار رٱئڌاْ مؽن ای نهانماو",
        "template-protected": "(تحت حمایت)",
        "template-semiprotected": "(نیمه حمایت وابیده)",
        "hiddencategories": "اي بلگأ یکي ز أندوما {{PLURAL:$1|1 hidden category|$1 hidden categories}} إ:",
        "nocreatetext": "{{SITENAME}}قابلیت درست کردن صفحات تازه را محدود کرده‌. ترین برگردین و صفحه‌ موجود را اصلاح کنین یا اینکه  [[Special:UserLogin|به سیستم داخل بوین یا حساب کاربری درست کنین]].",
+       "permissionserrors": "پٱلاْ:اْجازاْ ڤگرا",
        "permissionserrorstext-withaction": "ايسا سی نياگري $2 سإلا\nنارين {{PLURAL:$1|دلیل|دليلا}}:",
-       "recreate-moveddeleted-warn": "'''هشدار: ایسا در حال درست کردن دوباره صفحه‌ای هدین که قبلاً حذف وابیده '''در نظر داشته بوین که ادامه اصلاح ای صفحه کار درستی هده یا نه. نمایه حذف مربوط به ای صفحه سی راحتی کار در ادامه اویده",
+       "recreate-moveddeleted-warn": "'''هوشڌار: ایسا هنؽ سٱرزاْنۊ بٱلٛگی ناْ ؤرکل اْکونین کاْ نهاتٱرپاکسا آڤیڌاْ '''مؽن فرگ داشڌ ڤۊهین کاْ آلشڌ ای بٱلٛگاْ کارؽ دوروساْ هؽڌا آ نٱ. نمایاٛ پاکسا  مؽنڌار ڤا ای بٱلٛگاْ سی راهٱتی کار ڤا دینا آؤوڌاْ",
        "moveddeleted-notice": "ای بٱلٛیاْ پاکسا آڤیڌاْ،ؤرداوناْ سیاهؽ پاکسا،هناڌاری ۉ کلٛ کرڌن ای بٱلٛیاْ ؤرتی نهاڌ آڤیڌاْ",
        "content-model-wikitext": "ڤيکي تکست",
        "content-model-javascript": "جاڤا إسکريپت",
+       "undo-failure": "سی نڤیڌن سلۊکی ڤا آلشڌکاریٱل مؽنجخائی ای آلشڌکاریناْ نؽڤۊ بؽ هرنڳ کرڌ",
        "viewpagelogs": "نشودادن نمایه ها سی ای صفحه",
        "currentrev": "نسخه جاری",
        "currentrev-asof": "آخرين ڤانيأري جۈر $1",
        "revdelete-log": "دلیل:",
        "mergehistory-from": "بألگإ سرچشمأ:",
        "mergehistory-reason": "دلیل:",
+       "mergelog": "سیائاْ ؤریٱک",
        "history-title": "دڤارتإ دیئن ڤيرگار $1",
        "difference-title": "فرخ مإنجقا ڤانإیريا \"$1\"",
        "lineno": "سطر $1:",
        "timezoneregion-asia": "آسيا",
        "yourrealname": "نام واقعی:",
        "prefs-help-realname": "ذکر نام واقعی اختیاریه ایر تصمیم به گدن بگیرین هنگام ارجاع به آثارتو و انتساب هونو به ایسا زه نام واقعیتو استفاده ابوه",
+       "group-bot": "روڤاتٱل",
        "grouppage-sysop": "{{ns:project}}:مدیران",
        "right-writeapi": "سي نڤشدن اي پ آی ڤأنين ڤاکار",
        "newuserlogpage": "راسد ڤابیه وا کاریار",
        "rightslog": "نمایه حقوق کاربر",
        "action-edit": "ای بلگٱ نٱ ۋیرایشد کو",
+       "action-createaccount": "ڤاکل ای هساْڤ مؽنتوری",
        "nchanges": "$1 {{PLURAL:$1|تغییر|تغییرات}}",
        "enhancedrc-history": "ڤیرگار",
        "recentchanges": "تغییرات اخیر",
        "recentchanges-label-unpatrolled": "ای ويرايشت هنی تيه واداشت نوابيه",
        "recentchanges-label-plusminus": "أندازإ بألگأ ب شومار اي بایتا آلشد کردإ.",
        "recentchanges-legend-heading": "<strong>میراث:</strong>",
-       "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (Ù\87Ù\85Ú\86Û\8cÙ\86Ù\88 Ø³Ø§Ù\9bÛ\8cÙ\84 [[Special:بÙ\84Ú¯Ù±Û\8cÙ±Ù\84 ØªØ§Ø²Ù±|Ù\86Ù\88Ù\85Ú¯Ù± Ø¨Ù\84Ú¯Ù±Û\8cÙ±Ù\84 ØªØ§Ø²Ø§Ù\9b]]) Ú©Ù\88Ù\86Û\8cÙ\86",
+       "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (Ù\87Ù±Ú\86Ù\88Ù\86Ù\88Ý© Ú¤Ø§Ù\92[[Special:NewPages|Ù\81اÙ\9bئرست Ø¨Ù±Ù\84Ù\9bÚ¯Û\8cÙ±Ù\84 Ù\86Û\8a]] Ø³Ø§Ù\9bÙ\84 Ø¤Ù\88Ù±Ù\86Û\8cÙ\86)",
        "rcnotefrom": "ۋازیر {{PLURAL:$5|ۋیرایشد|ۋیرایشدا}}ز ۋیرگار strong>$3, $4</strong> تا ۋیرگار <strong>$1</strong>  دیاری اٛکونن .",
        "rclistfrom": "نشودادن تغییرات تازه با شروع زه $3 $2",
        "rcshowhideminor": "اصلاحات کوچیک $1",
        "recentchangeslinked-feed": "تغییرات مرتبط",
        "recentchangeslinked-toolbox": "تغییرات مرتبط",
        "recentchangeslinked-title": "تغییرهای مرتبط با $1",
-       "recentchangeslinked-summary": "Ù\86Ù\88Ù\85 Û\8cÙ± Ø¨Ù\84گاÙ\9b Ù\86Ù± Ú¤Ø§Ù\85ؽ Ú©Ù\88Ù\86Û\8cÙ\86 ØªØ§ Ø¢Ù\84Ø´Ú\8cکارÛ\8c Ø¨Ù±Ù\84Ù\9bگاÙ\9bÛ\8cÙ±Ù\84 Ú©Ø§Ù\92 Ú¤Ø§Ù\92 Ù\87Ù\88Ù\85ؽÙ\86اÙ\9bÚ\8cار Ú©Ø±Ú\8cÛ\8cÙ\86 Û\8cا Ø²Ø§Ù\92 Ù\87Ù\88 Ù\85ؽÙ\86اÙ\9bÚ\8cارÛ\8c Ú¯Ø±Û\8cÚ\8cÛ\8cÙ\86 Ù\86اÙ\92 Ú¤Ú¤Û\8cÙ\86Û\8cÙ\86(سÛ\8c Ù\86Û\8cٱشÚ\8cÙ\86 Ù\87Ù\88Ù\85 Ú¤Ù±Ù\86Ù\88Ù\86اÙ\92 Û\8cÙ± Ø¨Ù\86Ú©Û\8a Ú\86Ù\88Ù\86Ù\88Ý© Ú\86Û\8c Ø§Ø¦Û\8c Ù\86اÙ\92 Ú¤Ø²Ù±Ù\86Û\8cÙ\86\86Ù\88Ù\85 Ø¨Ù\88Ù\86Ú©Û\8a). \nØ¢Ù\84Ø´Ú\8cکارÛ\8cÙ±Ù\84 Ø¨Ù\84گاÙ\9bÛ\8cÙ±Ù\84ؽ Ú©Ø§Ù\92 Ù\85ؽÙ\86 Ø³Û\8cائاÙ\9b Ø³Ø§Ù\9bÛ\8cÙ\84 Ø¨Ø±Ú¯ Ø§Û\8cسا Ø¨Û\8aÙ\86 Ú\86Ù\88Ù\86Ù\88 <strong>Ù\85اÙ\9bÙ\86 Ù¾Ù\88ر</strong> Ø¯Û\8cارÛ\8c Ø§Ù\9bÚ©Ù\88Ù\86Ù\86.",
+       "recentchangeslinked-summary": "Ù\86Ù\88Ù\85 Û\8cÙ± Ø¨Ù±Ù\84Ù\9bگاÙ\92 Ù\86اÙ\92 Ú¤Ø§Ù\85ؽ Ú©Ù\88Ù\86Û\8cÙ\86 ØªØ§ Ø¢Ù\84Ø´Ú\8cکارÛ\8c Ø¨Ù±Ù\84Ù\9bÚ¯Û\8cÙ±Ù\84 Ú©Ø§Ù\92 Ú¤Ø§Ù\92 Ù\87Ù\88Ù\85ؽÙ\86Ú\8cارکرÚ\8cÛ\8cÙ\86اÙ\92 Û\8cا Ø²Ø§Ù\92 Ù\87Ù\88 Ù\85ؽÙ\86Ú\8cارÛ\8c Ú¯Ø±Û\8cÚ\8cÛ\8cÙ\86 Ù\86اÙ\92 Ú¤Ú¤Û\8cÙ\86Û\8cÙ\86(سÛ\8c Ù\86Û\8cٱشÚ\8cÙ\86 Ù\87Ù\88Ù\85 Ú¤Ù±Ù\86Ù\88Ù\86اÙ\92 Û\8cÙ± Ø¨Ù\86Ú©Û\8a Ú\86Ù\88Ù\86Ù\88Ý© Ú\86Û\8c Ø§Ø¦Û\8c Ù\86اÙ\92 Ú¤Ø²Ù±Ù\86Û\8cÙ\86\86Ù\88Ù\85 Ø¨Ù\88Ù\86Ú©Û\8a). \nØ¢Ù\84Ø´Ú\8cکارÛ\8cÙ±Ù\84 Ø¨Ù\84Ù\9bÚ¯Û\8cÙ±Ù\84ؽ Ú©Ø§Ù\92 Ù\85ؽÙ\86[[Special:Watchlist|Ù\81اÙ\9bئرست Ø¯Û\8cÙ\86اگرÛ\8cÙ±Ù\84 Ø§Ø½Ø³Ø§]] Ù\87ؽÚ\8cÙ\86 <strong>ؤٱرÚ\8cار</strong> Ù\86Ù\85اÛ\8c Ø§Ù\92Ú¤Û\8aÙ\87اÙ\92",
        "recentchangeslinked-page": "نوم بألگە:",
        "recentchangeslinked-to": "آلشتیایی که د بلگه یا هوم پیوند بینه وه جا بلگه دئیه بیه نشو بیه",
        "upload": "آپلود فایل",
        "filehist-datetime": "تاریخ/زمان",
        "filehist-thumb": "عسگ کۈچير وابيه",
        "filehist-thumbtext": "كۈچير کردن سی نوسقإ $1",
+       "filehist-nothumb": "نڤیڌن بٱن کلکی",
        "filehist-user": "کاربر",
        "filehist-dimensions": "ابعاد",
        "filehist-filesize": "اندازه فایل",
        "randomredirect": "تغییر مسیر اتفاقی",
        "statistics": "آمار",
        "doubleredirects": "تغییر مسیر دوبله",
+       "double-redirect-fixer": "ساموݩکار آلشڌتورٱل",
        "brokenredirects": "تغییرمسیرهای اشکسته وخراو",
        "withoutinterwiki": "صفحات بدون لینک های زبانی میان ویکی",
        "fewestrevisions": "صفحات با کمترین تعداداصلاحات وتجدیدنظرها",
        "booksources": "منابع کتاب",
        "booksources-search-legend": "پی جۈري سي سرچشمإیل کتاڤ",
        "booksources-search": "پی جۈري",
-       "specialloguserlabel": "کاربر:",
-       "speciallogtitlelabel": "عÙ\86Ù\88ان:",
+       "specialloguserlabel": "مؽنتور:",
+       "speciallogtitlelabel": "اÙ\88Ú\8cÚ¤ان:",
        "log": "نمایه ها",
        "all-logs-page": "گشڌنمائیٱل",
        "allpages": "همه صفحات",
        "allarticles": "همه صفحات",
        "allpagessubmit": "برو",
        "allpagesprefix": "نشو دادن صفحات همراه با پیشوند:",
+       "allpages-hide-redirects": "بؽ دیارنیڌن آلشڌتورٱل",
        "categories": "دسته آ",
        "emailuser": "امیل ای کاربر",
        "watchlist": "لیسڌ دیناگریٱل مو",
        "mywatchlist": "سإیل برگ",
+       "watchlistfor2": "سی $1 $2",
        "addedwatchtext": "صفحه «<nowiki>$1</nowiki>» به [[Special:Watchlist|لیست پی‌گیری‌های ]] ایسا\nاضاف وابید.\nتغییرات این صفحه و صفحه صحبت مر بوطه اش در آینده ایچو لیست ابوه. به‌علاوه، ای صفحه، سی واضح‌تر دیده وابیدن در [[Special:RecentChanges|فهرست تغییرات اخیر]] به شکل <b>سیاه</b> ایا.\n\nایر بعدا خواستین ای  صفحه زه لیست پی‌گیریهاتو ورداشته بوه، رو «'''عدم پی‌گیری'''» در بالای صفحه کلیک کنین.",
        "removedwatchtext": "آن صفحه\"[[:$1]]\" جابجا وابیده زه[[Special:لیست پیگیری|لیست پیگیری ایسا]].",
        "watch": "پی‌گیری",
        "watchthispage": "پیگیری ای صفحه",
        "unwatch": "پيگري نبيڎه",
-       "watchlist-details": "{{PLURAL:$1|$1 صفحه|$1 صفحات}} درلیست پیگیری ایسا, صفحات صحبت حساب نیبوه.",
-       "wlshowlast": "نمایش آخرین $1 ساعت $2 روز",
+       "watchlist-details": "{{PLURAL:$1|$1 بٱلٛگاْ|$1 بٱلٛگیٱل}} مؽن لیست دیناگری ایسا,هؽڌا",
+       "wlheader-showupdated": "بٱلٛگیٱلؽ کاْ دیناتٱر زاْ آخرین ساٛل اؽسا آلشڌ آڤیڌ ناْ<strong>پورٱنڳ</strong> نماونیڌاْ آڤیڌناْ",
+       "wlnote": "ڤاْ لٱم {{PLURAL:$1|آلشڌؽ|<strong>$1</strong> آلشڌؽ}} کاْ ڤاْ {{PLURAL:$2|سات|<strong>$2</strong> سات}} رٱئڌاْ انجوم آڤؽڌ مۉجۊڌ هؽڌا،ؤرگار دوکرٱت ڤینی دینائی: $3، $4",
+       "wlshowlast": "نماونیڌن ٱخیری $1 سات $2 رۊز",
+       "watchlist-options": "دزاٛیٱل دیناگری",
        "watching": "مئن حالت پي جوري",
        "unwatching": "درحالت عدم پیگیری...",
+       "enotif_reset": "دزاٛکرڌن گشڌ بٱلٛگیٱل ڤاْ اوڌڤان نیٱشڌ آڤیڌاْ",
        "deletepage": "حذف صفحه",
        "historywarning": "توجه: آن صفحه ای که ایسا اخوین حذف کنین گزارش تاریخی داره:",
        "confirmdeletetext": "ایسا اخوین یه صفحه بلند با همه گزارش تاریخی هونه حذف کنین.\nلطفا کانفیرم یا تائید کنین که تمایل وقصد ای کار را دارین, وایسا دوین یا می دانید نتایج وآثار ای کار را, و ایسا انجام ادین ای کار را مطابق با [[{{MediaWiki:Policy-url}}|سیاست‌ها]].",
        "rollbacklinkcount": "چڤاسإ کردن $1 {{PLURAL:$1|ویرایشت|ویرایشتیا}}",
        "protectlogpage": "نمایه حفاظت وحمایت",
        "protectedarticle": "پٱر و پیم ڤابیڌاٛ \"[[$1]]\"",
+       "modifiedarticleprotection": "بارت هناگری«[[$1]]» ناْ آلشڌنیڌ",
        "prot_1movedto2": "[[$1]] جابجا وابید به[[$2]]",
        "protectcomment": "دلیل:",
        "protectexpiry": "سپری وابیڎه ا:",
        "mycontris": "هومياریا",
        "anoncontribs": "هومياریا",
        "contribsub2": "سی {{GENDER:$3|$1}} ($2)",
+       "nocontribs": "هیچ آلشڌؽ ڤا ای دیاریٱل جوساْ نڤیڌ",
        "uctop": "تازاٛ باۋ",
        "month": "در این ماه (و قبل زه آن):",
        "year": "در ای سال (وقبل زه آن):",
        "blockip": "بستن کاربر",
        "ipboptions": "۲ ساعت:2 hours,۱ روز:1 day,۳ روز:3 days,۱ هفته:1 week,۲ هفته:2 weeks,۱ ماه:1 month,۳ ماه:3 months,۶ ماه:6 months,۱ سال:1 year,بی‌نهایت:infinite",
        "ipblocklist": "آدرسهای  آی پی وکاربران بسته وابیدند",
+       "infiniteblock": "بؽ تٱ",
        "blocklink": "بسته بوه !",
        "unblocklink": "باز بوه",
        "contribslink": "ھأیاری",
        "blocklogpage": "نمایه _ بسته‌وابیده‌ها",
        "blocklogentry": "بسته وابید [[$1]] با سپری وابیدن وقت زه $2 $3",
+       "reblock-logentry": "ساموکارٱل بورسناْ دٱسرٱسی [[$1]]آلشڌ ڤاْ تٱهاْ بورسناْ دٱسرٱسی مؽن $2 $3",
+       "block-log-flags-nocreate": "هرنڳ ؤرکلنیڌن هساڤ بؽ هرنڳ آڤیڌ",
+       "proxyblocker": "ؤٱرڤٱناْ پروکسی",
        "movepagetext": "با استفاده زه فرم زیر نام صفحه تغییر اکنه و همه گزارش تاریخی هو به نام تازه جابجا ابوه.\nعنوان کهنه تبدیل به یک صفحه تغییر مسیر به عنوان جدید ابوه.\nایسا ترین بطور اتوماتیک تغییر مسیر های مربوط به عنوان اصلی رو به روز رسانی کنین. ایر ایسا مطمئن نهدین با دیدن یونو مطمئن بوین:\n[[Special:تغییر مسیر دوبل|دوبل ]] یا[[Special:تغییرمسیر خروا یا اشکسته|تغییرمسیرهای خراو یا اشکسته]].\n\nلینکهایی که به عنوان صفحه قدیمی هدن تغییر نه کنن حتماً تغییر مسیرهای دوبل یا اشکسته و خراو را بررسی کنین.\n'''ایسا''' مسئول اطمینان زه یو هدین که لینکها هنی به همان‌جایی که قرار هده برن.\n\nتوجه کنین که ایر زه قبل صفحه‌ای در عنوان تازه وجود داشته بوه صفحه منتقل '''نه بوه'''،\nمیر یو که آن صفحه خالی یا تغییر مسیر بوه و گزارش تاریخی اصلاح نداشته بوه.\n یعنی ایر اشتباه کردین ترین صفحه را به همان جایی که زه هو جابجا وابیدین برگردانین، و  نترین رو صفحات موجود بنویسین\n\n'''هشدار!'''\nجابجایی صفحات به نام تازه ممکنه  تغییر کلی و غیرمنتظره‌ای سی\n صفحات دوست داشتنی داشته بوه ؛\nلطفاً مطمئن بوین که قبل زه جابجا کردن صفحه، عواقب ای کار را درک اکنین.",
        "movepagetalktext": "صفحه صحبت مربوط، ایر وجود داشته بوه، بطور اتوماتیک همراه با صفحه اصلی\n جابجا ابوه '''میر یو که''' :\n* در حال جابجایی صفحه زه ای فضای نام به فضای نام دیگری بوین،\n* یه صفحه صحبت غیرخالی تحت ای نام تازه وجود داشته بوه، یا\n* کادر زیر را تیک نزده بوین.\n\nدر ای موارد، وا صفحه را بطور دستی جابجا کرده و یا محتویات دو صفحه را با اصلاح ادغام کنین.",
        "newtitle": "به عنوان تازه:",
        "pageinfo-title": "ڌونائی زاْ «$1»",
        "pageinfo-header-basic": "ڌونائیٱل بٱلٛگاْ",
        "pageinfo-header-restrictions": "هناڌاری زاْ بٱلٛگاْ",
+       "pageinfo-header-properties": "ؤیژگیٱل بٱلٛگاْ",
        "pageinfo-display-title": "نماونیڌن داسوݩ",
        "pageinfo-default-sort": "کاٛلیت یٱتورنیڌن نهافٱرز",
        "pageinfo-length": "هنا بٱلگاْ (ڤایڌ)",
        "pageinfo-language": "زڤون هؽلنیڌیٱل بٱلٛگاْ",
        "pageinfo-content-model": "بارت هؽلنیڌاْ بٱلٛگاْ",
        "pageinfo-robot-policy": "فاٛئرست کرڌن ڤا روڤات",
+       "pageinfo-robot-index": "موجاز",
+       "pageinfo-robot-noindex": "بؽ اعتڤار",
        "pageinfo-watchers": "شوماراٛ دیناگروناٛ بٱلٛگاْ",
+       "pageinfo-few-watchers": " ساوا تٱر زاْ $1 {{PLURAL:$1| دیناگری|دیناگری}}",
        "pageinfo-redirects-name": "بور آلشڌ لۊرٱل ڤاْ ای بٱلٛگاْ",
+       "pageinfo-subpages-name": "زؽر بٱلٛگیٱل ای بٱلٛگاْ",
+       "pageinfo-subpages-value": "1 ($2 {{PLURAL:$2|آلشڌتور|آلشڌ تور}}; $3 {{PLURAL:$3|خاٛراز آلشڌ تور|خاٛراز آلشڌ تور}})",
+       "pageinfo-magic-words": "{{PLURAL:$1|قساْ|قساْ}} جادۊیی ($1)",
+       "pageinfo-hidden-categories": "{{PLURAL:$1| بنکۊ|بنکۊ}} بؽ دیار ( $1 )",
+       "pageinfo-templates": "{{PLURAL:$1|اولگۊیٱل|اولگۊیٱل}} اْسفاڌاْ آڤیڌاْ ($1)",
        "pageinfo-toolboxlink": "دونسمندیا بلگه",
+       "pageinfo-contentpage": "باٛئنٱت آڤیڌاْ ڤاْ اوڌڤان بٱلٛگاْ موهتٱڤائی",
+       "pageinfo-contentpage-yes": "هٱراٛ",
+       "patrol-log-page": "سیاهاْ لاٛر",
        "previousdiff": "← اصلاح قدیمی",
        "nextdiff": "تفاوت بعدی→",
        "file-info-size": "$1 × $2 پیکسل, اندازه فایل: $3, MIME نوع: $4",
+       "file-info-size-pages": "<span style=\"direction:ltr\">$1 × $2</span> نوخڌاْ، مٱشلٱقی پٱرڤٱناْ: $3، نوع MIME پٱرڤٱناْ: $4، $5 بٱلٛگاْ",
        "file-nohires": "قابلیت تفکیک بالاتری در دسترس نه.",
        "svg-long-desc": "SVG فایل, تقریبا$1 × $2 پیکسل, اندازه فایل: $3",
        "show-big-image": "جانیا اصلی",
        "namespacesall": "همه",
        "monthsall": "همه ماهها",
        "semicolon-separator": "؛&#32;",
+       "imgmultipagenext": "بٱلٛگاْ دینائی",
+       "imgmultigo": "رۉڤا",
+       "imgmultigoto": "رٱئڌن ڤاٛ بٱلٛگاْ $1",
+       "watchlisttools-clear": "پاکسانیڌن فاٛئرسڌ دیناگری",
        "watchlisttools-view": "نشودادن تغییرات مربوطه",
        "watchlisttools-edit": "نشودادن واصلاح کردن لیست پیگیریها",
        "watchlisttools-raw": "اصلاح لیست خام پی‌گیری‌ها",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|چک چنه]])",
        "version": "ترجمه یا تفسیر",
+       "redirect": "آلشد لۊر ڤا پٱرڤٱناْ،مؽنتور،بٱلٛگاْ دبارنوماْ نوسخاْ",
        "specialpages": "بلگاٛ آ ۋیجٱ",
-       "tag-filter": "[[Special:سٱرڌیسا|سٱرديس]] فيلتر:",
+       "tag-filter": "[[Special:سٱرڌیسا|سٱرديس]]فیلتر:",
        "tag-list-wrapper": "[[Special:Tags|{{PLURAL:$1|سرديس|سرديسا}}]]: $2",
        "logentry-delete-delete": "$1 بألگأ {{GENDER:$2|پاکسا ڤابيأ}} $3",
+       "logentry-delete-restore": "$1{{GENDER:$2|آلشڌ کرڌن}} بٱلٛگاْ ناْ$3سی$4",
+       "logentry-delete-revision": "$1 دیاری {{PLURAL:$5|یٱ نوسخاْ|$5 نوسخاْ}} بٱلٛگاْ $3 ناْ{{GENDER:$2|آلشڌکرڌ}}: $4",
+       "revdelete-content-hid": "هؽلنیڌناْ بؽڌیارکرڌ",
        "logentry-move-move": "$1 {{GENDER:$2|جا ب جا کردإ}} بألگأ $3 نأ سي $4",
        "logentry-move-move_redir": "$1 بٱلٛگاْ $3 ناْ ڤاْ $4 کاْ آلشڌ تور ڤیڌاْ {{GENDER:$2|کل کرڌ}}",
        "logentry-patrol-patrol-auto": "$1 نوسخهٔ $4 بٱلٛگاْ $3 ناْ خودکار ڤاْ عنڤان لرخوؤٱرداٛ {{GENDER:$2|دزاْ کونین}}",
        "logentry-newusers-create": "هساڤ کارياري $1 {{GENDER:$2|راسد ڤابي}}",
+       "logentry-newusers-autocreate": "حساڤ $1  ڤاْ بارت خوڌکار {{GENDER:$2|ؤرکل آڤیڌاْ}}",
        "logentry-upload-upload": "$1 {{GENDER:$2|سوڤار کرده}} $3",
        "searchsuggest-search": "جستن {{SITENAME}}",
-       "duration-days": "$1 رۊز"
+       "duration-days": "$1 رۊز",
+       "randomrootpage": "بٱلٛگاْ بونی تٱساڌفی"
 }
index 405b970..9263654 100644 (file)
        "tooltip-preferences-save": "Uložit nastavení",
        "tooltip-summary": "Zadejte stručné shrnutí",
        "interlanguage-link-title": "$1 – $2",
-       "common.css": "/* Zde uvedené CSS bude ovlivňovat všechny styly */",
+       "common.css": "/* Zde uvedené CSS bude ovlivňovat všechny vzhledy */",
        "print.css": "/* Zde uvedené CSS bude ovlivňovat tiskový výstup */",
        "noscript.css": "/* Zde uvedené CSS bude ovlivňovat uživatele s vypnutým JavaScriptem */",
        "group-autoconfirmed.css": "/* Zde uvedené CSS bude ovlivňovat pouze automaticky schválené uživatele */",
        "group-sysop.css": "/* Zde uvedené CSS bude ovlivňovat pouze správce */",
        "group-bureaucrat.css": "/* Zde uvedené CSS bude ovlivňovat pouze byrokraty */",
        "common.json": "/* Zde uvedený JSON se načte pro všechny uživatele při načtení každé stránky. */",
-       "common.js": "/* Zde uvedený JavaScript bude použit pro všechny uživatele při načtení každé stránky. */",
+       "common.js": "/* Zde uvedený JavaScript bude použit pro všechny uživatele při načtení každé stránky */",
        "group-autoconfirmed.js": "/* Zde uvedený JavaScript bude použit pouze pro automaticky schválené uživatele */",
        "group-user.js": "/* Zde uvedený JavaScript bude použit pouze pro registrované uživatele */",
        "group-bot.js": "/* Zde uvedený JavaScript bude použit pouze pro roboty */",
index 2943e3c..d0cb901 100644 (file)
        "resetpass-abort-generic": "Die Passwortänderung wurde durch eine Erweiterung abgebrochen.",
        "resetpass-expired": "Dein Passwort ist abgelaufen. Bitte lege ein neues Passwort zur Anmeldung fest.",
        "resetpass-expired-soft": "Dein Passwort ist abgelaufen und muss geändert werden. Bitte wähle jetzt ein neues Passwort aus oder klicke auf „{{int:authprovider-resetpass-skip-label}}“, um es später zu ändern.",
+       "resetpass-validity": "Dein Passwort ist nicht gültig: $1\n\nBitte lege zur Anmeldung ein neues Passwort fest.",
        "resetpass-validity-soft": "Dein Passwort ist ungültig: $1\n\nBitte wähle jetzt ein neues Passwort aus oder klicke auf „{{int:authprovider-resetpass-skip-label}}“, um es später zu ändern.",
        "passwordreset": "Passwort zurücksetzen",
        "passwordreset-text-one": "Fülle dieses Formular aus, um ein temporäres Passwort per E-Mail zu erhalten.",
index d6f4dd1..f007af9 100644 (file)
        "about": "Heqa",
        "article": "Wesiqe",
        "newwindow": "(pençerey newey de beno a)",
-       "cancel": "Annuler",
+       "cancel": "Bıtexelne",
        "moredotdotdot": "Vêşi...",
        "morenotlisted": "Na lista qay kemi ya.",
        "mypage": "Per",
index 3425056..f7c5315 100644 (file)
        "resetpass-abort-generic": "Password change has been aborted by an extension.",
        "resetpass-expired": "Your password has expired. Please set a new password to log in.",
        "resetpass-expired-soft": "Your password has expired and needs to be changed. Please choose a new password now, or click \"{{int:authprovider-resetpass-skip-label}}\" to change it later.",
+       "resetpass-validity": "Your password is not valid: $1\n\nPlease set a new password to log in.",
        "resetpass-validity-soft": "Your password is not valid: $1\n\nPlease choose a new password now, or click \"{{int:authprovider-resetpass-skip-label}}\" to change it later.",
        "passwordreset": "Reset password",
        "passwordreset-text-one": "Complete this form to receive a temporary password via email.",
        "blockedtext": "<strong>Your username or IP address has been blocked.</strong>\n\nThe block was made by $1.\nThe reason given is <em>$2</em>.\n\n* Start of block: $8\n* Expiration of block: $6\n* Intended blockee: $7\n\nYou can contact $1 or another [[{{MediaWiki:Grouppage-sysop}}|administrator]] to discuss the block.\nYou cannot use the \"{{int:emailuser}}\" feature unless a valid email address is specified in your [[Special:Preferences|account preferences]] and you have not been blocked from using it.\nYour current IP address is $3, and the block ID is #$5.\nPlease include all above details in any queries you make.",
        "autoblockedtext": "Your IP address has been automatically blocked because it was used by another user, who was blocked by $1.\nThe reason given is:\n\n:<em>$2</em>\n\n* Start of block: $8\n* Expiration of block: $6\n* Intended blockee: $7\n\nYou may contact $1 or one of the other [[{{MediaWiki:Grouppage-sysop}}|administrators]] to discuss the block.\n\nNote that you may not use the \"{{int:emailuser}}\" feature unless you have a valid email address registered in your [[Special:Preferences|user preferences]] and you have not been blocked from using it.\n\nYour current IP address is $3, and the block ID is #$5.\nPlease include all above details in any queries you make.",
        "systemblockedtext": "Your username or IP address has been automatically blocked by MediaWiki.\nThe reason given is:\n\n:<em>$2</em>\n\n* Start of block: $8\n* Expiration of block: $6\n* Intended blockee: $7\n\nYour current IP address is $3.\nPlease include all above details in any queries you make.",
+       "actionblockedtext": "You have been blocked from performing this action.",
        "blockednoreason": "no reason given",
        "whitelistedittext": "Please $1 to edit pages.",
        "confirmedittext": "You must confirm your email address before editing pages.\nPlease set and validate your email address through your [[Special:Preferences|user preferences]].",
        "blockip": "Block {{GENDER:$1|user}}",
        "blockiptext": "Use the form below to block write access from a specific IP address or username.\nThis should be done only to prevent vandalism, and in accordance with [[{{MediaWiki:Policy-url}}|policy]].\nFill in a specific reason below (for example, citing particular pages that were vandalized).\nYou can block IP address ranges using the [https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing CIDR] syntax; the largest allowed range is /$1 for IPv4 and /$2 for IPv6.",
        "ipaddressorusername": "IP address or username:",
-       "ipbexpiry": "Expiration:",
        "ipbreason": "Reason:",
        "ipbreason-dropdown": "*Common block reasons\n** Inserting false information\n** Removing content from pages\n** Spamming links to external sites\n** Inserting nonsense/gibberish into pages\n** Intimidating behavior/harassment\n** Abusing multiple accounts\n** Unacceptable username",
        "ipb-hardblock": "Prevent logged-in users from editing from this IP address",
-       "ipbcreateaccount": "Prevent account creation",
-       "ipbemailban": "Prevent user from sending email",
+       "ipbcreateaccount": "Account creation",
+       "ipbemailban": "Sending email",
        "ipbenableautoblock": "Automatically block the last IP address used by this user, and any subsequent IP addresses they try to edit from",
        "ipbsubmit": "Block this user",
        "ipbother": "Other time:",
        "ipboptions": "2 hours:2 hours,1 day:1 day,3 days:3 days,1 week:1 week,2 weeks:2 weeks,1 month:1 month,3 months:3 months,6 months:6 months,1 year:1 year,indefinite:infinite",
        "ipbhidename": "Hide username from edits and lists",
        "ipbwatchuser": "Watch this user's user and talk pages",
-       "ipb-disableusertalk": "Prevent this user from editing their own talk page while blocked",
+       "ipb-disableusertalk": "Editing their own talk page",
        "ipb-change-block": "Re-block the user with these settings",
        "ipb-confirm": "Confirm block",
        "ipb-sitewide": "Sitewide",
        "ipb-partial": "Partial",
-       "ipb-type-label": "Type",
        "ipb-pages-label": "Pages",
        "badipaddress": "Invalid IP address",
        "blockipsuccesssub": "Block succeeded",
        "ipb-blocklist": "View existing blocks",
        "ipb-blocklist-contribs": "Contributions for {{GENDER:$1|$1}}",
        "ipb-blocklist-duration-left": "$1 left",
+       "block-actions": "Actions to block:",
+       "block-expiry": "Expiration:",
+       "block-options": "Additional options:",
+       "block-prevent-edit": "Editing",
+       "block-reason": "Reason:",
+       "block-target": "Username or IP address:",
        "unblockip": "Unblock user",
        "unblockiptext": "Use the form below to restore write access to a previously blocked IP address or username.",
        "ipusubmit": "Remove this block",
index f1bee24..b7a4257 100644 (file)
        "returnto": "Volver a $1.",
        "tagline": "De {{SITENAME}}",
        "help": "Ayuda",
+       "help-mediawiki": "Ayuda sobre MediaWiki",
        "search": "Buscar",
        "search-ignored-headings": " #<!-- dejar esta línea exactamente como está --> <pre>\n# Títulos que serán ignorados por la búsqueda.\n# Los cambios estarán en vigor tan pronto como la página con el título esté indexada.\n# Puedes forzar la reindexación de una página haciendo una edición nula.\n# La sintaxis es la siguiente:\n#   * Todo lo que sigue a un carácter \"#\" hasta el final de la línea, es un comentario.\n#   * Todas las líneas que no están en blanco son los títulos exactos que se ignorarán (diferenciando mayúsculas de minúsculas).\nReferencias\nEnlaces externos\nVéase también\n #</pre> <!-- dejar esta línea exactamente como está -->",
        "searchbutton": "Buscar",
        "editpage-invalidcontentmodel-text": "El modelo de contenido «$1» no se admite.",
        "editpage-notsupportedcontentformat-title": "Formato de contenido no compatible",
        "editpage-notsupportedcontentformat-text": "El formato de contenido $1 no es compatible con el modelo de contenido $2.",
+       "slot-name-main": "Principal",
        "content-model-wikitext": "texto wiki",
        "content-model-text": "texto sin formato",
        "content-model-javascript": "JavaScript",
        "grant-delete": "Borrar páginas, revisiones y entradas del registro",
        "grant-editinterface": "Editar el espacio de nombres MediaWiki y las páginas CSS/JSON/JavaScript del sitio y de los usuarios",
        "grant-editmycssjs": "Editar tu CSS/JSON/JavaScript",
-       "grant-editmyoptions": "Editar tus preferencias de usuario",
+       "grant-editmyoptions": "Editar tus preferencias de usuario y configuración en formato JSON",
        "grant-editmywatchlist": "Editar tu lista de seguimiento",
        "grant-editsiteconfig": "Editar páginas de configuración CSS/JS del sitio",
        "grant-editpage": "Editar páginas existentes",
        "rcfilters-watchlist-edit-watchlist-button": "Edita tu lista de seguimiento",
        "rcfilters-watchlist-showupdated": "Los cambios hechos en páginas que no has visitado desde que se efectuaron aparecen en <strong>negrita</strong>, acompañados de marcadores sólidos.",
        "rcfilters-preference-label": "Usar la interfaz sin JavaScript",
-       "rcfilters-preference-help": "Cargar cambios recientes sin filtros ni la funcionalidad de resaltado.",
+       "rcfilters-preference-help": "Carga los cambios recientes sin filtros de búsqueda ni funcionalidad de resaltado.",
        "rcfilters-watchlist-preference-label": "Usar interfaz sin JavaScript",
-       "rcfilters-watchlist-preference-help": "Cargar la lista de seguimiento sin filtros ni la funcionalidad de resaltado.",
+       "rcfilters-watchlist-preference-help": "Carga la lista de seguimiento sin filtros de búsqueda ni la funcionalidad de resaltado.",
        "rcfilters-filter-showlinkedfrom-label": "Mostrar cambios en páginas enlazadas desde",
        "rcfilters-filter-showlinkedfrom-option-label": "<strong>Páginas enlazadas desde</strong> la página seleccionada",
        "rcfilters-filter-showlinkedto-label": "Mostrar cambios en páginas que enlazan a",
index a0ffaca..70ff246 100644 (file)
        "exif-photometricinterpretation-1": "Чорны і белы (чорны — 0)",
        "exif-photometricinterpretation-3": "Палітра",
        "exif-photometricinterpretation-4": "Маска празрыстасьці",
+       "exif-photometricinterpretation-5": "Падзелены (імаверна, CMYK)",
+       "exif-photometricinterpretation-8": "CIE L*a*b*",
        "exif-unknowndate": "Невядомая дата",
        "exif-orientation-1": "Звычайная",
        "exif-orientation-2": "Адлюстраваная па гарызанталі",
index 91e6110..37d33f2 100644 (file)
        "exif-compression-4": "CCITT Group 4 codification fax",
        "exif-copyrighted-true": "Sub copyright",
        "exif-copyrighted-false": "Stato de copyright non definite",
+       "exif-photometricinterpretation-0": "Nigro e blanco (blanco es 0)",
        "exif-photometricinterpretation-1": "Nigre e blanc (0 pro nigre)",
+       "exif-photometricinterpretation-3": "Paletta",
+       "exif-photometricinterpretation-4": "Masca de transparentia",
+       "exif-photometricinterpretation-5": "Separate (probabilemente CMYK)",
+       "exif-photometricinterpretation-8": "CIE L*a*b*",
+       "exif-photometricinterpretation-9": "CIE L*a*b* (codification ICC)",
+       "exif-photometricinterpretation-10": "CIE L*a*b* (codification ITU)",
        "exif-unknowndate": "Data incognite",
        "exif-orientation-1": "Normal",
        "exif-orientation-2": "Invertite horizontalmente",
index 5dbe4ac..73eb361 100644 (file)
@@ -9,7 +9,8 @@
                        "Ua2004",
                        "Ата",
                        "Максим Підліснюк",
-                       "Тест"
+                       "Тест",
+                       "Piramidion"
                ]
        },
        "exif-imagewidth": "Ширина",
        "exif-compression-34712": "JPEG2000",
        "exif-copyrighted-true": "Охороняється законом про авторське право",
        "exif-copyrighted-false": "Авторські права не встановлено",
+       "exif-photometricinterpretation-0": "Чорний і білий (Білий — це 0)",
        "exif-photometricinterpretation-1": "Чорний і білий (білий — 0)",
        "exif-photometricinterpretation-2": "RGB",
+       "exif-photometricinterpretation-3": "Палітра",
+       "exif-photometricinterpretation-4": "Маска прозорості",
+       "exif-photometricinterpretation-5": "Відокремлено (ймовірно CMYK)",
        "exif-photometricinterpretation-6": "YCbCr",
+       "exif-photometricinterpretation-8": "CIE L*a*b*",
+       "exif-photometricinterpretation-9": "CIE L*a*b* (ICC-кодування)",
+       "exif-photometricinterpretation-10": "CIE L*a*b* (ITU-кодування)",
        "exif-unknowndate": "Невідома дата",
        "exif-orientation-1": "Нормальна",
        "exif-orientation-2": "Відображено по горизонталі",
index e6d9304..42ec243 100644 (file)
        "accmailtext": "Un mot de passe généré aléatoirement pour [[User talk:$1|$1]] a été envoyé à $2.\nIl peut être modifié sur la page ''[[Special:ChangePassword|Changement de mot de passe]]'' après connexion.",
        "newarticle": "(Nouveau)",
        "newarticletext": "Vous avez suivi un lien vers une page qui n’existe pas encore. \nAfin de créer cette page, entrez votre texte dans la boîte ci-après (vous pouvez consulter [$1 la page d’aide] pour plus d’informations). \nSi vous êtes arrivé{{GENDER:||e}} ici par erreur, cliquez sur le bouton <strong>Retour</strong> de votre navigateur.",
-       "anontalkpagetext": "----\n<em>Vous êtes sur la page de discussion d’un utilisateur anonyme qui n’a pas encore créé de compte ou qui n’en utilise pas</em>.\nPour cette raison, nous devons utiliser son adresse IP pour les identifier.\nUne adresse IP peut être partagée par plusieurs utilisateurs.\nSi vous êtes un{{GENDER:||e|}} utilisat{{GENDER:|eur|rice|eur}} anonyme et si vous constatez que des commentaires qui ne vous concernent pas vous ont été adressés, vous pouvez [[Special:CreateAccount|créer un compte]] ou [[Special:UserLogin|vous connecter]] afin d’éviter toute confusion future avec d’autres contributeurs anonymes.",
+       "anontalkpagetext": "----\n<em>Vous êtes sur la page de discussion d’un utilisateur anonyme qui n’a pas encore créé de compte ou qui n’en utilise pas</em>.\nPour cette raison, nous devons utiliser son adresse IP pour l'identifier.\nUne telle adresse IP peut être partagée par plusieurs utilisateurs.\nSi vous êtes un{{GENDER:||e|}} utilisat{{GENDER:|eur|rice|eur}} anonyme et si vous constatez que des commentaires qui ne vous concernent pas vous ont été adressés, vous pouvez [[Special:CreateAccount|créer un compte]] ou [[Special:UserLogin|vous connecter]] afin d’éviter toute confusion future avec d’autres contributeurs anonymes.",
        "noarticletext": "Il n’y a pour l’instant aucun texte sur cette page.\nVous pouvez [[Special:Search/{{PAGENAME}}|lancer une recherche sur ce titre]] dans les autres pages,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} rechercher dans les opérations liées]\nou [{{fullurl:{{FULLPAGENAME}}|action=edit}} créer cette page]</span>.",
        "noarticletext-nopermission": "Il n'y a pour l'instant aucun texte sur cette page.\nVous pouvez [[Special:Search/{{PAGENAME}}|faire une recherche sur ce titre]] dans les autres pages,\nou <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} rechercher dans les journaux associés]</span>, mais vous n'avez pas la permission de créer cette page.",
        "missing-revision": "La révision nº $1 de la page intitulée « {{FULLPAGENAME}} » n’existe pas.\n\nCela survient en général en suivant un lien historique désuet vers une page qui a été supprimée.\nVous pouvez trouver plus de détails dans le [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} journal des suppressions].",
        "special-characters-title-emdash": "tiret cadratin",
        "special-characters-title-minus": "signe moins",
        "mw-widgets-abandonedit": "Êtes-vous sûr de vouloir quitter le mode d’édition sans enregistrer d’abord ?",
-       "mw-widgets-abandonedit-discard": "Ignorer les modifications",
+       "mw-widgets-abandonedit-discard": "Annuler les modifications",
        "mw-widgets-abandonedit-keep": "Continuer à modifier",
        "mw-widgets-abandonedit-title": "Êtes-vous sûr ?",
        "mw-widgets-dateinput-no-date": "Aucune date sélectionnée",
index 3185b8d..f76e312 100644 (file)
@@ -74,8 +74,8 @@
        "thu": "Ham",
        "fri": "Jum",
        "sat": "Sah",
-       "january": "Januwari",
-       "february": "Peburuari",
+       "january": "Januari",
+       "february": "Pebruari",
        "march": "Maret",
        "april": "April",
        "may_long": "Mei",
@@ -86,7 +86,7 @@
        "october": "Oktober",
        "november": "Nopember",
        "december": "Desember",
-       "january-gen": "Januwari",
+       "january-gen": "Januari",
        "february-gen": "Pebruari",
        "march-gen": "Maret",
        "april-gen": "April",
        "newmessagesdifflinkplural": "{{PLURAL:$1|biloli'o|999=u biloli'o}} pulitiyo",
        "youhavenewmessagesmulti": "Yio lo'otapu tahuli bohu to $1",
        "editsection": "boli'a",
-       "editold": "boli'",
+       "editold": "boli'a",
        "viewsourceold": "Bilohi bungoliyo",
        "editlink": "boli'a",
        "viewsourcelink": "Bilohi bungoliyo",
index 09aad7a..0dcec29 100644 (file)
        "resetpass-abort-generic": "שינוי הסיסמה בוטל על־ידי הרחבה.",
        "resetpass-expired": "סיסמתך פקעה. נא להגדיר סיסמה חדשה כדי להיכנס.",
        "resetpass-expired-soft": "הסיסמה שלך פקעה, וצריך לשנות אותה. יש לבחור סיסמה חדשה כעת, או ללחוץ על \"{{int:authprovider-resetpass-skip-label}}\" כדי לשנות אותה מאוחר יותר.",
+       "resetpass-validity": "סיסמתך אינה חוקית: $1\n\nנא להגדיר סיסמה חדשה כדי להיכנס.",
        "resetpass-validity-soft": "הסיסמה שלך אינה תקינה: $1\n\nיש לבחור סיסמה חדשה כעת או ללחוץ על \"{{int:authprovider-resetpass-skip-label}}\" כדי לשנות את הסיסמה מאוחר יותר.",
        "passwordreset": "איפוס סיסמה",
        "passwordreset-text-one": "יש למלא טופס זה כדי לקבל סיסמה זמנית בדוא\"ל.",
index 0c2ea95..3a9bd34 100644 (file)
        "anoneditwarning": "<strong>Figyelem:</strong> Nem vagy bejelentkezve. Ha szerkesztesz, az IP-címed nyilvánosan látható lesz a laptörténetben. Ha <strong>[$1 bejelentkezel]</strong> vagy <strong>[$2 regisztrálsz]</strong>, a szerkesztéseid a felhasználónevedhez lesznek társítva, egyéb hasznos dolgok mellett.",
        "anonpreviewwarning": "''Nem vagy bejelentkezve. A mentéskor az IP-címed rögzítve lesz a laptörténetben.''",
        "missingsummary": "'''Emlékeztető:''' Nem adtál meg szerkesztési összefoglalót. Ha összefoglaló nélkül akarod elküldeni a szöveget, kattints újra a mentésre.",
-       "selfredirect": "<strong>Figyelem:</strong> Az lapot önmagára készült átirányítani. Lehet, hogy rossz célt adtál meg, vagy rossz oldalt szerkesztesz. Ha ismét a $1 gombra kattintasz, akkor az átirányítás mégis létrejön.",
+       "selfredirect": "<strong>Figyelem:</strong> A lapot önmagára készülöd átirányítani. Lehet, hogy rossz célt adtál meg, vagy rossz oldalt szerkesztesz. Ha ismét a „$1” gombra kattintasz, az átirányítás mégis létrejön.",
        "missingcommenttext": "Kérjük, írj összefoglalót a szerkesztésedhez.",
        "missingcommentheader": "<strong>Emlékeztető:</strong> Nem adtad meg a megjegyzés tárgyát.\nHa ismét a „$1” gombra kattintasz, akkor a szerkesztésed nélküle lesz elmentve.",
        "summary-preview": "A szerkesztési összefoglaló előnézete:",
        "seconds": "{{PLURAL:$1|egy|$1}} másodperccel",
        "minutes": "{{PLURAL:$1|egy|$1}} perccel",
        "hours": "{{PLURAL:$1|egy|$1}} órával",
-       "days": "$1 nappal",
+       "days": "$1 nap",
        "weeks": "{{PLURAL:$1|$1 hét|$1 hét}}",
        "months": "{{PLURAL:$1|$1 hónap|$1 hónap}}",
        "years": "{{PLURAL:$1|$1 év|$1 év}}",
index d5632f8..86d1008 100644 (file)
@@ -12,7 +12,8 @@
                        "Macofe",
                        "Fitoschido",
                        "Baloch Khan",
-                       "Muhraz"
+                       "Muhraz",
+                       "Ardzun"
                ]
        },
        "tog-underline": "Garih bawahi tautan:",
        "permalink": "Pautan parmanen",
        "print": "Cetak",
        "view": "Baco",
+       "view-foreign": "Caliak di $1",
        "edit": "Suntiang",
        "create": "Buek",
+       "create-local": "Tambah sumber deskripsi lokal",
        "delete": "Hapuih",
        "undelete_short": "Batal hapuih $1 {{PLURAL:$1|suntiangan}}",
        "viewdeleted_short": "Lihek {{PLURAL:$1|$1 suntiangan}} nan dihapuih",
        "otherlanguages": "Dalam bahaso lain",
        "redirectedfrom": "(Dialiahkan dari $1)",
        "redirectpagesub": "Laman pangaliahan",
+       "redirectto": "Dialiahkan ka",
        "lastmodifiedat": "Laman ko tarakhia diubah pado $2, tanggal $1.",
        "viewcount": "Laman ko lah dicaliak {{PLURAL:$1|$1 kali}}.",
        "protectedpage": "Laman nan dilinduangi",
        "actionthrottled": "Tindakan tabateh",
        "actionthrottledtext": "Sanak tabateh untuak malakuan tindakan ko banyak-banyak dalam wakatu singkek. Cubo lah laik satalah bara minit.",
        "protectedpagetext": "Laman ko alah dikunci untuak manghindari panyuntiangan.",
-       "viewsourcetext": "Sanak dapek malihek atau manyalin sumber laman iko:",
+       "viewsourcetext": "Sanak dapek mancaliak atau manyalin sumber laman iko:",
        "viewyourtext": "Sanak dapek mancaliak jo mangkopi sumber untuak \"suntiangan sanak\" ka laman ko",
        "protectedinterface": "Laman ko baisi teks antarmuko untuak digunoan dek parangkaik lunak di wiki ko sajo, dan alah dikunci untuak maindaan kasalahan. \nUntuak manambah atau maubah tajamahan di kasado wiki, harap gunoan [https://translatewiki.net/ translatewiki.net], yaitu proyek palokalan MediaWiki.",
        "editinginterface": "'''Paringatan:''' Sanak manyuntiang laman nan digunoan untuak manyadiokan teks antarmuko untuak parangkaik lunak.\nParubahan teks ko akan mampangaruhi tampilan pado antarmuko pangguno untuak pangguno lain.\nUntuak tajamahan, harap gunoan [https://translatewiki.net/wiki/Main_Page?setlang=min translatewiki.net], proyek palokalan MediaWiki.",
        "userlogin-noaccount": "Alun ado akun?",
        "userlogin-joinproject": "Join {{SITENAME}}",
        "createaccount": "Buek akun",
-       "userlogin-resetpassword-link": "Buek ulang kato sandi",
+       "userlogin-resetpassword-link": "Lupo kato sandi Sanak?",
+       "userlogin-helplink2": "Bantuan masuak log",
        "createacct-emailrequired": "Alamaik surel",
        "createacct-emailoptional": "Alamaik surel (opsional)",
        "createacct-email-ph": "Masuakan alamaik surel Sanak",
        "loginlanguagelabel": "Baso: $1",
        "suspicious-userlogout": "Pamintaan Sanak untuak kalua log ditulak karano tampaknyo dikirim oleh paramban nan rusak atau proksi panyinggah.",
        "pt-login": "Masuak log",
+       "pt-login-button": "Masuak log",
        "pt-createaccount": "Buek akun",
        "pt-userlogout": "Kalua log",
        "php-mail-error-unknown": "Kasalahan nan indak jaleh dalam fungsi mail() PHP",
        "preview": "Caliak",
        "showpreview": "Pratonton",
        "showdiff": "Parubahan",
-       "anoneditwarning": "'''Ingek:''' Sanak alun masuak log.\nAlamat IP sanak tacatat pado riwayaik suntiangan laman ko.",
+       "anoneditwarning": "'''Ingek:''' Sanak alun masuak log.\nAlamat IP sanak tacatat pado riwayaik suntiangan laman ko. Kok Sanak <strong>[$1 log in]</strong> atau <strong>[$2 mambuek akun]</strong>, suantiang Sanak ka didistribusian kapado namo pangguno Sanak, sarato baragam kauntuangan lainnyo.",
        "anonpreviewwarning": "''Sanak alun masuak log. Manyimpan laman akan manyababkan alamaik IP Sanak tacatat pado riwayat suntiangan laman iko.''",
        "missingsummary": "'''Paringatan:''' Sanak indak mamasuakan ringkasan panyuntiangan. Jikok Sanak baliak manakan tombol Simpan, suntiangan Sanak akan disimpan tanpa ringkasan panyuntiangan.",
        "missingcommenttext": "Masuakan komentar Sanak di bawah ko.",
        "permissionserrorstext": "Sanak indak ado hak untuak malakuannyo dek {{PLURAL:$1|alasan}} barikuik:",
        "permissionserrorstext-withaction": "Sanak indak punyo hak akses untuak $2, dek {{PLURAL:$1|alasan}} barikuik:",
        "recreate-moveddeleted-warn": "'''Ingek: Sanak mambuek ulang suatu laman nan alah dihapuih.'''\n\nHarap ditimbang apo rancak malanjuikan suntiangan Sanak.\nBarikuik ko log pangapuihan jo pamindahan dari laman ko:",
-       "moveddeleted-notice": "Laman ko alah dihapuih.\nSabagai reperensi, barikuik adolah log pangapuihan dan pamindahannyo.",
+       "moveddeleted-notice": "Laman ko alah dihapuih.\nSabagai referensi, barikuik adolah log pangapuihan jo pamindahannyo.",
        "log-fulllog": "Liek saluruah log",
        "edit-hook-aborted": "Suntiangan dibatalan samo kait parser\ntanpa ado katarangan.",
        "edit-gone-missing": "Indak dapek mampabarui laman.\nMungkin alah dihapuih.",
        "histlegend": "Bandiangan piliahan: Tandoi revisi untuak mambandiangan dan takan enter atau tombol di bawah.<br />\nContoh: '''({{int:cur}})''' = bedo jo versi tarakhia, '''({{int:last}})''' = bedo jo versi sabalunnyo, '''{{int:minoreditletter}}''' = suntiangan ketek.",
        "history-fieldset-title": "Talusuri riwayaik",
        "history-show-deleted": "Hanyo nan dihapuih",
-       "histfirst": "Nan lamo",
-       "histlast": "Nan baru",
+       "histfirst": "Nan paliang lamo",
+       "histlast": "Nan paliang baru",
        "historysize": "({{PLURAL:$1|$1  bita}})",
        "historyempty": "(kosong)",
        "history-feed-title": "Riwayat revisi",
        "shown-title": "Tampilkan $1 {{PLURAL:$1|hasil}} per laman",
        "viewprevnext": "Caliak ($1 {{int:pipe-separator}} $2) ($3)",
        "searchmenu-exists": "'''Ado laman nan banamo \"[[:$1]]\" pado wiki ko.'''",
-       "searchmenu-new": "'''Buek laman \"[[:$1]]\" di wiki ko!'''",
+       "searchmenu-new": "<strong>Buek laman \"[[:$1]]\" di wiki ko!</strong> {{PLURAL:$2|0=|Caliak pulo laman nan ditamukan dari pancarian Sanal.|Caliak pulo hasia pancarian nan ditamukan.}}",
        "searchprofile-articles": "Laman isi",
        "searchprofile-images": "Multimedia",
        "searchprofile-everything": "Sadonyo",
        "searchrelated": "bakaitan",
        "searchall": "sado",
        "showingresults": "Di bawah ko dikaluaan sampai {{PLURAL:$1|'''$1''' hasil}}, dimulai dari #'''$2'''.",
+       "search-showingresults": "{{PLURAL:$4|Hasia <strong>$1</strong> dari <strong>$3</strong>|Hasia <strong>$1 - $2</strong> dari <strong>$3</strong>}}",
        "search-nonefound": "Indak ado hasil nan cocok sasuai jo parmintaan",
        "powersearch-legend": "Pencarian lanjut",
        "powersearch-ns": "Mancari di ruangnamo:",
        "action-writeapi": "manggunoan panulisan API",
        "action-import": "impor laman ko dari wiki lain",
        "nchanges": "$1 {{PLURAL:$1|parubahan}}",
+       "enhancedrc-history": "riwayaik",
        "recentchanges": "Parubahan baru",
        "recentchanges-legend": "Piliahan parubahan baru",
        "recentchanges-summary": "Caliak parubahan tabaru pado wiki di laman ko.<br />\n;Patunjuak:(<span style=\"color:blue;\">bedo</span>) parubahan, (<span style=\"color:blue;\">sijarah</span>) riwayaik parubahan, '''B''' laman baru, '''b''' suntiangan bot, '''k''' suntiangan ketek, <span class=\"unpatrolled\">!</span> parubahan alun dipatroli,<br /><span style=\"color:green;\">'''(+ ''bita'')'''</span> isi laman batambah, <span style=\"color:red;\">(- ''bita'')</span> isi laman bakurang, (← Ikhtisar otomatih), (→ <span style=\"color:grey;\">Suntiangan bagian</span>)",
+       "recentchanges-noresult": "Indak ado parubahan dalam rantang wakatu ko nan sasuai jo kriteria.",
        "recentchanges-feed-description": "Tamuan parubahan baru dalam umpan wiki ko",
        "recentchanges-label-newpage": "Suntiangan ko mambuek laman baru",
        "recentchanges-label-minor": "Iko suntiangan ketek",
        "rcnotefrom": "Di bawah ko ado parubahan mulai dari '''$2''' (sampai '''$1''' parubahan).",
        "rclistfrom": "Tunjuakan parubahan baru mulai dari tanggal $3 $2",
        "rcshowhideminor": "$1 suntiangan ketek",
+       "rcshowhideminor-show": "Tunjuakan",
+       "rcshowhideminor-hide": "Suruakan",
        "rcshowhidebots": "$1 bot",
+       "rcshowhidebots-show": "Tunjuakan",
+       "rcshowhidebots-hide": "Suruakan",
        "rcshowhideliu": "$1 pangguno tadaftar",
+       "rcshowhideliu-show": "Tunjuakan",
+       "rcshowhideliu-hide": "Suruakan",
        "rcshowhideanons": "$1 pangguno anon",
+       "rcshowhideanons-show": "Tunjuakan",
+       "rcshowhideanons-hide": "Suruakan",
        "rcshowhidepatr": "$1 suntiangan tapatroli",
        "rcshowhidemine": "$1 suntiangan denai",
+       "rcshowhidemine-show": "Tunjuakan",
+       "rcshowhidemine-hide": "Suruakan",
        "rclinks": "Tunjuakkan $1 parubahan tabaru dalam $2 hari nan tarakhia",
        "diff": "bedo",
        "hist": "sijarah",
        "imagelinks": "Panggunoan berkas",
        "linkstoimage": "{{PLURAL:$1|Halaman|$1 halaman}} nan iko manggunoan berkas nan iko:",
        "linkstoimage-more": "Labiah dari $1 {{PLURAL:$1|laman}} ado pautan ka berkas ko.\nDaftar barikuik manunjuakan {{PLURAL:$1|$1 laman jo pautan langsuang}} ka berkas ko.\nAdo juo tasadio [[Special:WhatLinksHere/$2|daftar langkoknyo]].",
-       "nolinkstoimage": "Indak ado laman nan bapauik ka berkas ko.",
+       "nolinkstoimage": "Indak ado laman nan manggunokan berkas ko.",
        "morelinkstoimage": "Caliak [[Special:WhatLinksHere/$1|pautan baliak]] ka berkas ko.",
        "linkstoimage-redirect": "$1 (pangaliahan berkas) $2",
        "duplicatesoffile": "Sabanyak {{PLURAL:$1|$1 berkas barikuik}} marupoan duplikat dari berkas ko ([[Special:FileDuplicateSearch/$2|rincian labiah lanjuik]]):",
        "apisandbox": "Bak kasiak API",
        "booksources": "Sumber buku",
        "booksources-search-legend": "Cari di sumber buku",
+       "booksources-search": "Cari",
        "specialloguserlabel": "Pangguno:",
        "speciallogtitlelabel": "Target (judul atau pangguno):",
        "log": "Log",
        "contributions": "Jariah {{GENDER:$1|pangguno}}",
        "contributions-title": "Jariah pangguno untuak $1",
        "mycontris": "Jariah",
-       "contribsub2": "Untuak $1 ($2)",
+       "anoncontribs": "Jariah",
+       "contribsub2": "Untuak {{GENDER:$3|$1}} ($2)",
        "uctop": "kini",
        "month": "Dari bulan (dan sabalunnyo):",
        "year": "Dari taun (dan sabalunnyo):",
        "sp-contributions-search": "Cari jariah",
        "sp-contributions-username": "Alamaik IP atau namo pangguno:",
        "sp-contributions-toponly": "Hanyo manampilan suntiangan nan tarakhia",
+       "sp-contributions-newonly": "Hanyo manampilan suntiangan nan tarakhia",
        "sp-contributions-submit": "Cari",
        "whatlinkshere": "Pautan baliak",
        "whatlinkshere-title": "Laman nan takaik ka \"$1\"",
        "importstart": "Mangimpor laman...",
        "importnosources": "Indak ado sumber impor transwiki nan lah dibuek dan pamuatan riwayaik sacaro langsuang alah dinon-aktipan.",
        "importlogpagetext": "Administrasi laman impor jo riwayaik panyuntiangannyo dari wiki lain.",
-       "tooltip-pt-userpage": "Laman pangguno Sanak",
+       "tooltip-pt-userpage": "Laman {{GENDER:|pangguno Sanak}}",
        "tooltip-pt-anonuserpage": "Laman pangguno IP Sanak",
-       "tooltip-pt-mytalk": "Laman rundiang Sanak",
+       "tooltip-pt-mytalk": "Laman rundiang {{GENDER:|Sanak}}",
        "tooltip-pt-anontalk": "Parundiangan tantang suntiangan dari IP ko",
-       "tooltip-pt-preferences": "Pangaturan denai",
+       "tooltip-pt-preferences": "Piliahan {{GENDER:|Sanak}}",
        "tooltip-pt-watchlist": "Daftar laman nan dipantau.",
-       "tooltip-pt-mycontris": "Daftar jariah Sanak",
+       "tooltip-pt-mycontris": "Daftar jariah {{GENDER:|Sanak}}",
        "tooltip-pt-login": "Sanak disaranan untuak masuak log; walaupun indak wajib",
        "tooltip-pt-logout": "Kalua log",
        "tooltip-pt-createaccount": "Sanak dianjuaan mambuek akun dan masuak log; walaupun hal iko indak aruih",
        "tooltip-t-recentchangeslinked": "Parubahan baru laman nan bakaik jo laman ko",
        "tooltip-feed-rss": "Umpan RSS untuak laman ko",
        "tooltip-feed-atom": "Umpan Atom untuak laman ko",
-       "tooltip-t-contributions": "Caliak daftar jariah pangguno ko",
+       "tooltip-t-contributions": "Daftar kontribusi {{GENDER:$1|pangguno iko}}",
        "tooltip-t-emailuser": "Kirimkan surel pado pangguno ko",
        "tooltip-t-upload": "Muek berkas",
        "tooltip-t-specialpages": "Daftar kasado laman istimewa",
        "logentry-newusers-autocreate": "Akun pangguno $1 alah {{GENDER:$2|dibuek}} sacaro otomatih",
        "logentry-rights-rights": "$1 {{GENDER:$2|maubah}} kaanggotaan kalompok $3 dari $4 manjadi $5",
        "logentry-rights-rights-legacy": "$1 {{GENDER:$2|maubah}} kaanggotaan kalompok $3",
+       "logentry-upload-upload": "$1 {{GENDER:$2|mangunggah}} $3",
        "rightsnone": "(indak ado)",
        "searchsuggest-search": "Cari {{SITENAME}}",
        "searchsuggest-containing": "Barisi...",
index 2349b20..b799bf3 100644 (file)
        "resetpass-abort-generic": "Generic error message shown on [[Special:ChangePassword]] when an extension aborts a password change from a hook.",
        "resetpass-expired": "Generic error message shown on [[Special:ChangePassword]] when a user's password is expired",
        "resetpass-expired-soft": "Generic warning message shown on [[Special:ChangePassword]] when a user needs to reset their password, but they are not prevented from logging in at this time",
+       "resetpass-validity": "Warning message shown on [[Special:ChangePassword]] when a user needs to reset their password, because their password is not valid.\n\nParameters:\n* $1 - error message",
        "resetpass-validity-soft": "Warning message shown on [[Special:ChangePassword]] when a user needs to reset their password, because their password is not valid.\n\nRefers to {{msg-mw|authprovider-resetpass-skip-label}}.\n\nParameters:\n* $1 - error message",
        "passwordreset": "Title of [[Special:PasswordReset]].\n{{Identical|Reset password}}",
        "passwordreset-text-one": "Text on [[Special:PasswordReset]] that appears when there is only one way of resetting the password.\n\n{{msg-mw|Passwordreset-text-many}} will be used, when there are multiple ways of resetting the password.",
        "blockedtext": "Text displayed to blocked users.\n\n\"email this user\" should be consistent with {{msg-mw|Emailuser}}.\n\nParameters:\n* $1 - the blocking sysop (with a link to his/her userpage)\n* $2 - the reason for the block\n* $3 - the current IP address of the blocked user\n* $4 - (Unused) the blocking sysop's username (plain text, without the link)\n* $5 - the unique numeric identifier of the applied autoblock\n* $6 - the expiry of the block\n* $7 - the intended target of the block (what the blocking user specified in the blocking form)\n* $8 - the timestamp when the block started\nSee also:\n* {{msg-mw|Grouppage-sysop}}\n* {{msg-mw|Autoblockedtext|notext=1}}\n* {{msg-mw|Systemblockedtext|notext=1}}",
        "autoblockedtext": "Text displayed to automatically blocked users.\n\n\"email this user\" should be consistent with {{msg-mw|Emailuser}}.\n\nParameters:\n* $1 - the blocking sysop (with a link to his/her userpage)\n* $2 - the reason for the block (in case of autoblocks: {{msg-mw|autoblocker}})\n* $3 - the current IP address of the blocked user\n* $4 - (Unused) the blocking sysop's username (plain text, without the link). Use it for GENDER.\n* $5 - the unique numeric identifier of the applied autoblock\n* $6 - the expiry of the block\n* $7 - the intended target of the block (what the blocking user specified in the blocking form)\n* $8 - the timestamp when the block started\nSee also:\n* {{msg-mw|Grouppage-sysop}}\n* {{msg-mw|Blockedtext|notext=1}}\n* {{msg-mw|Systemblockedtext|notext=1}}",
        "systemblockedtext": "Text displayed to requests blocked by MediaWiki configuration.\n\n\"email this user\" should be consistent with {{msg-mw|Emailuser}}.\n\nParameters:\n* $1 - (Unused) A dummy user attributed as the blocker, possibly as a link to a user page.\n* $2 - the reason for the block\n* $3 - the current IP address of the blocked user\n* $4 - (Unused) the dummy blocking user's username (plain text, without the link).\n* $5 - A short string indicating the type of system block.\n* $6 - the expiry of the block\n* $7 - the intended target of the block\n* $8 - the timestamp when the block started\nSee also:\n* {{msg-mw|Grouppage-sysop}}\n* {{msg-mw|Blockedtext|notext=1}}\n* {{msg-mw|Autoblockedtext|notext=1}}",
+       "actionblockedtext": "Text displayed when a user is blocked from perofmring an action, but no matching block for the user exists. This can happen if an extension forces a user to be blocked.",
        "blockednoreason": "Substituted with <code>$2</code> in the following message if the reason is not given:\n* {{msg-mw|cantcreateaccount-text}}.\n{{Identical|No reason given}}",
        "whitelistedittext": "Used as error message. Parameters:\n* $1 - a link to [[Special:UserLogin]] with {{msg-mw|loginreqlink}} as link description\n* $2 - an URL to the same\n\nSee also:\n* {{msg-mw|Nocreatetext}}\n* {{msg-mw|Uploadnologintext}}\n* {{msg-mw|Loginreqpagetext}}",
        "confirmedittext": "Used as error message.",
        "blockip": "Used as the text of a link in the sidebar toolbox. Clicking this link takes you to [[Special:Block]], with a relevant username or IP address (e.g. \"Username\" on [[User talk:Username]], [[Special:Contributions/Username]], etc.) already filled in.\n\nParameters:\n* $1 - username, for GENDER support\n{{Identical|Block user}}",
        "blockiptext": "Used in the {{msg-mw|Blockip}} form in [[Special:Block]].\n\nRefers to {{msg-mw|Policy-url}}.\n\nThis message may follow the message {{msg-mw|Ipb-otherblocks-header}} and other block messages.\n\nParameters:\n* $1 - CIDR suffix of the largest allowed IPv4 block (as an integer)\n* $2 - CIDR suffix of the largest allowed IPv6 block (as an integer)\n\nSee also:\n* {{msg-mw|Unblockiptext}}",
        "ipaddressorusername": "{{Identical|IP address or username}}",
-       "ipbexpiry": "{{Identical|Expiry}}",
        "ipbreason": "Label of the block reason dropdown in [[Special:BlockIP]] and the unblock reason textfield in [{{fullurl:Special:IPBlockList|action=unblock}} Special:IPBlockList?action=unblock].\n\n{{Identical|Reason}}",
        "ipbreason-dropdown": "Used as item list for dropdown on [[Special:Block]].\n\nThe label for this dropdown is {{msg-mw|Ipbreason}}.",
        "ipb-hardblock": "This is the label for a checkbox in the user block form on [[Special:Block]].\n\nSee also:\n* {{msg-mw|ipbemailban}}\n* {{msg-mw|ipb-disableusertalk}}\n* {{msg-mw|ipbenableautoblock}}\n* {{msg-mw|ipbhidename}}\n* {{msg-mw|ipbwatchuser}}",
        "ipb-confirm": "Used as hidden field in the form on [[Special:Block]].",
        "ipb-sitewide": "A type of block the user can select from on [[Special:Block]].",
        "ipb-partial": "A type of block the user can select from on [[Special:Block]].",
-       "ipb-type-label": "The label of the type of editing restriction the admin would like to impose on [[Special:Block]].",
        "ipb-pages-label": "The label for an autocomplete text field to specify pages to block a user from editing on [[Special:Block]].",
        "badipaddress": "An error message shown when one entered an invalid IP address in blocking page.",
        "blockipsuccesssub": "Used as page title in [[Special:Block]].\n\nThis message is the subject for the following message:\n* {{msg-mw|Blockipsuccesstext}}",
        "ipb-blocklist": "Used as link text in [[Special:Block]].\n\nThe link points to Specil:BlockList.",
        "ipb-blocklist-contribs": "Used in [[Special:Block]].\n* $1 - target username",
        "ipb-blocklist-duration-left": "Used on [[Special:BlockList]] to show the remaining time (years, months, days, hours, minutes) until the block expires.\n$1 - The duration left",
+       "block-actions": "Label for the checkboxes for specifying the actions that a block affects on [[Special:Block]]",
+       "block-expiry": "Label for the input for specifying the expiry time of a block on [[Special:Block]]",
+       "block-options": "Label for the checkboxes for specifying additional options for a block on [[Special:Block]]",
+       "block-prevent-edit": "Label for the checkbox for specifying an editing block in [[Special:Block]].",
+       "block-reason": "Label for the input for specifying the reason for a block on [[Special:Block]]",
+       "block-target": "Label for the input for specifying the target of a block on [[Special:Block]]",
        "unblockip": "Used as title and legend for the form in [[Special:Unblock]].",
        "unblockiptext": "Used in the {{msg-mw|Unblockip}} form on [[Special:Unblock]].",
        "ipusubmit": "Used as button text on [{{canonicalurl:Special:BlockList|action=unblock}} Special:BlockList?action=unblock]. To see the message:\n* Go to [[Special:BlockList]]\n* Click \"unblock\" for any block (but you can only see \"unblock\" if you have administrator rights)\n* It is now the button below the form",
index 8a0931f..9dffd59 100644 (file)
        "help": "Faaba",
        "help-mediawiki": "Faaba MediaWiki ga",
        "search": "Ceeci",
+       "search-ignored-headings": "#<!-- ma ši žeero woo barmay kul --> <pre>\n# Tammaasawey kaŋ ceeciyanoo g'i muray.\n# Ne barmawey ga huru goy ra za moɲoo nda tammaasaa šilbandi.\n# War ga hin ka moɲoo gaabi k'a šilbay taaga nda war na barmayyan koonu tee.\n# Nahawoo ti sanda:\n#   * Haya kul kaŋ hun \"#\" harfu ga hala žeeri benantaa ga ti daara.\n#   * Žeeri kaŋ ši koonu kul ti maa alhakiika kaŋ ga murandi, šigira azzaati da goo a ra.\nFellawey\nTarayhere dobey\nGuna da\n #</pre> <!-- ma ši žeero woo barmay kul -->",
        "searchbutton": "Ceeci",
        "go": "Koy",
        "searcharticle": "Koy",
        "toolbox": "Goyjinawey",
        "tool-link-userrights": "{{GENDER:$1|goykaw}} batey barmay",
        "tool-link-userrights-readonly": "{{GENDER:$1|goykaw}} batey guna",
+       "tool-link-emailuser": "Bataga sanba {{GENDER:$1|goykaa woo}} se",
        "imagepage": "Tuku moo guna",
        "mediawikipage": "Bataga moo guna",
        "templatepage": "Leeti moo guna",
        "redirectedfrom": "(Kaŋ $1 n'a bisandi)",
        "redirectpagesub": "Bisandi moo",
        "redirectto": "Bisandi ne:",
-       "lastmodifiedat": "Moɲoo barmay cee koraa $1 hane, $2 waate.",
+       "lastmodifiedat": "Moɲoo woo barmay koraa tee $1 hane, $2 waate.",
        "viewcount": "Moɲoo woo duwandi {{PLURAL:$1|cee foo|$1 cee booboyaŋ}}.",
        "protectedpage": "Moo jejebante",
        "jumpto": "Sar ne:",
        "protectedinterface": "Moɲoo woo ka hantumoo cebe wikiyoo woo porogaramoo se nd'a ga jejebu hasaraw teekey ga. Goy nda [https://translatewiki.net/ translatewiki.net], MediaWiki berandiyan porožewoo ka berandiyaŋ tonton wala k'i barmay.",
        "editinginterface": "<strong>Yaamar:</strong> War goo ma moo fasal kaŋ nd'i ga goy ka goyjinaa porogaram hantumoo cebe. \nBarmawey kaŋ ga tee moɲoo ka hantumey kaŋ goykaw ga dii y'ey bere wikiyoo woo ga.",
        "translateinterface": "Ka berandiyan tonton wala k'i barmay, wa goy nda [https://translatewiki.net/ translatewiki.net], MediaWiki berandiyan porožewoo.",
-       "cascadeprotected": "Moɲoo woo ga jejebu barmayyan ga zam'a goo ne {PLURAL:$1|moo kaŋ ti|moɲey kaŋ ti}} jejebante nda \"kaŋandiyan\" suubari kaŋ ga dira: \n$2",
+       "cascadeprotected": "Moɲoo woo ga jejebu barmayyan ga zam'a goo ne {PLURAL:$1|moo kaŋ ti|moɲey kaŋ ti}} jejebante nda \"kaŋandiyan\" suubaroo kaŋ ga dira: \n$2",
        "namespaceprotected": "War šii nda fondo ka moɲey barmay <strong>$1</strong> maadogoo ra.",
        "customcssprotected": "War šii nda fondo ka CSS moɲoo woo barmay zama goykaw tana foo boŋkayandiyaney g'a ra.",
        "customjsprotected": "War šii nda fondo ka JavaScript barmay zama a goo nda goykaw tana foo boŋkayandiyaney.",
        "noemail": "Bataga aderesu kul mana jisandi \"$1\" goykaa se.",
        "noemailcreate": "War ga hima ka bataga aderesu henna noo.",
        "passwordsent": "Šennikufal taaga n' ka sanbandi bataga aderesu jisantaa ga  \"$1\" se. Ceeci ka huru koyne nda n' duu w'a.",
-       "blocked-mailpassword": "I na war IP aderesoo ganji a ma hantum, adiši a ši duu fondo ka goy nda šennikufal yeetiyan dabaroo ka zanbayan gagay.",
+       "blocked-mailpassword": "I na war IP aderesoo ganji a ma hantum. Ka zanbayan ganji, šennikufal yeetiyan ši duɲe IP aderesoo woo ga.",
        "eauthentsent": "Cimandiyan bataga n' ka sanbandi bataga aderesu tabatantaa ga. \nWar ga hima ka šilbawey kaŋ goo bataga laasaabu jina, hala war ga hin ka war ga hin ka bataga tana kul sanba ka cimandi kaŋ alhakiika ra kontoo ti war wane.",
        "throttled-mailpassword": "Šennikufal yeetiyan bataga n' ka sanbandi war se a ga too {{PLURAL:$1|guuru $1}}.\nKa zanbayan ganji, šennikufal yeetiyan bataga foo de ma sanbandi {{PLURAL:$1|guuru $1}} ra.",
        "mailerror": "Bataga sanbayan firka: $1",
-       "acct_creation_throttle_hit": "Boro kaŋ ga ciya nda war IP aderesoo na {{PLURAL:$1|kontu $1}} jirbi koraa ra, woo ti alkadaroo beero kaŋ ga hin ka tee waatoo woo ra. \nWoo sabboo se, borey kaŋ ga goy nda IP aderesoo woo ši hin ka kontu kul tee koyne sohõda.",
+       "acct_creation_throttle_hit": "Boro kaŋ ga ciya nda war IP aderesoo na {{PLURAL:$1|kontu foo|kontu $1}} jirbi $2 korawey ra, kaŋ ti alkadar kul ibeero kaŋ duɲe waatoo woo ra. \nAdiši, borey kaŋ ga goy nda IP aderesoo woo ši hin ka kontu kul tee koyne sohõda.",
        "emailauthenticated": "War bataga aderesoo n' ka tabatandi $2 boŋ $3 ga.",
        "emailnotauthenticated": "War bataga aderesoo mana tabatandi.\nBataga kul ši sanbandi alhaaley wey affoo ku še.",
        "noemailprefs": "Bataga aderesu tabatandi war ibaayey ra alhaaley wey ma hin ka goy.",
        "createaccount-title": "Kontu tee {{SITENAME}} se",
        "createaccount-text": "Boro foo na kontu tee war bataga aderesoo se {{SITENAME}} ($4) kaŋ maa ti \"$2\" ga, nda \"$3\" šennikufaloo.\nWa maa hantum ka hima ka huru nda šennikufal barmay sohõ.\n\nWar ga hin ka batagaa woo muray nda kontoo woo n' ka dere ka tee.",
        "login-throttled": "War ceeci cee booboyaŋ ka huru.\nTaare batu $1 jina ka ciya koyne.",
-       "login-abort-generic": "War mana hin ka huru - Laybu",
+       "login-abort-generic": "War mana hin ka huru",
        "login-migrated-generic": "War kontoo n' ka ganandi nda war goykaw maaɲoo ši ye ka bara wikiyoo woo ga.",
        "loginlanguagelabel": "Šenni:$1",
        "suspicious-userlogout": "War ceeciroo ka fatta mongu zam'a ga hima kaŋ ceecikaw kayraa wala tugudoo tokore k'a sanba.",
        "createacct-another-realname-tip": "Maa cimi noo nda n' ga baa.\nNda war soobay k'a noondi, boro ga goy nd'a ka goykaw alhaali noo ngi goyoo se.",
        "pt-login": "Huru",
        "pt-login-button": "Huru",
+       "pt-login-continue-button": "Gaabandi ka huru",
        "pt-createaccount": "Kontu tee",
        "pt-userlogout": "Fatta",
        "php-mail-error-unknown": "Firka šibayante PHP mail() goymee se",
        "newpassword": "Šennikufal taaga:",
        "retypenew": "Šennikufal taagaa hantum koyne:",
        "resetpass_submit": "Šennikufal barmay ka huru",
-       "changepassword-success": "War šennikufaloo barmay ka boori!",
+       "changepassword-success": "War šennikufaloo n' k barmay!",
        "changepassword-throttled": "War ceeci cee booboyaŋ ka huru.\nTaare batu $1 jina ka ceeci koyne.",
        "resetpass_forbidden": "Šennikufaley ši hin ka barmay",
        "resetpass-no-info": "War ga hima ka huru jina ka duu moɲoo woo.",
index 34054a3..2fb69b1 100644 (file)
        "limitreport-templateargumentsize-value": "$1/$2 ไบต์",
        "limitreport-expansiondepth": "ความลึกการขยายสูงสุด",
        "limitreport-unstrip-depth": "ความลึกการเรียกซ้ำ Unstrip",
+       "limitreport-unstrip-size-value": "$1/$2 ไบต์",
        "expandtemplates": "ขยายแม่แบบ",
        "expand_templates_output": "ผลลัพธ์",
        "expand_templates_ok": "ตกลง",
        "pagelang-select-lang": "เลือกภาษา",
        "pagelang-reason": "เหตุผล",
        "pagelang-submit": "ส่ง",
+       "pagelang-nonexistent-page": "ไม่มีหน้า $1",
+       "pagelang-unchanged-language": "ตั้งหน้า $1 เป็นภาษา $2 แล้ว",
+       "pagelang-unchanged-language-default": "ตั้งหน้า $1 เป็นภาษาเนื้อหาโดยปริยายของวิกิแล้ว",
+       "pagelang-db-failed": "ฐานข้อมูลไม่สามารถเปลี่ยนภาษาของหน้า",
        "right-pagelang": "เปลี่ยนภาษาหน้า",
        "action-pagelang": "เปลี่ยนภาษาหน้า",
        "log-name-pagelang": "ปูมการเปลี่ยนภาษา",
        "special-characters-group-khmer": "เขมร",
        "special-characters-group-canadianaboriginal": "แคนาดาพื้นเมืองดั้งเดิม",
        "special-characters-title-minus": "เครื่องหมายลบ",
-       "mw-widgets-abandonedit": "à¸\84ุà¸\93à¹\81à¸\99à¹\88à¹\83à¸\88วà¹\88าà¸\95à¹\89อà¸\87à¸\81ารà¸\81ลัà¸\9aà¹\84à¸\9b view mode à¹\82à¸\94ยà¹\84มà¹\88à¸\9aัà¸\99à¸\97ึà¸\81à¸\82à¹\89อมูลà¹\80อาà¹\84วà¹\89à¸\81à¹\88อà¸\99 ?",
+       "mw-widgets-abandonedit": "à¸\84ุà¸\93à¹\81à¸\99à¹\88à¹\83à¸\88วà¹\88าà¸\95à¹\89อà¸\87à¸\81ารออà¸\81à¸\88าà¸\81ภาวะà¹\81à¸\81à¹\89à¹\84à¸\82à¹\82à¸\94ยà¹\84มà¹\88à¸\9aัà¸\99à¸\97ึà¸\81à¸\81à¹\88อà¸\99หรือà¹\84มà¹\88",
        "mw-widgets-dateinput-no-date": "ไม่เลือกวันที่",
        "mw-widgets-usersmultiselect-placeholder": "เพิ่ม...",
        "mw-widgets-titlesmultiselect-placeholder": "เพิ่ม...",
index 1ca81b1..45f424a 100644 (file)
        "tog-watchdefault": "Men tahrirlagan sahifa va fayllar kuzatuv roʻyxatimga qoʻshilsin",
        "tog-watchmoves": "Men nomini koʻchirgan sahifa va fayllar kuzatuv roʻyxatimga qoʻshilsin",
        "tog-watchdeletion": "Men oʻchirgan sahifa va fayllarni kuzatuv roʻyxatimga qoʻsh",
-       "tog-minordefault": "Sukut boʻyicha barcha tahrirlarimni «kichik tahrir» etib belgilash",
+       "tog-watchuploads": "Yuklagan fayllarim kuzatuv roʻyxatimga qoʻshilsin",
+       "tog-watchrollback": "Tezda qaytarish harakatini amalga oshirganimdan keyin oʻsha sahifa kuzatuv roʻyxatimga qoʻshilsin",
+       "tog-minordefault": "Sukut boʻyicha barcha tahrirlarimni «kichik tahrir» deb belgilansin",
        "tog-previewontop": "Koʻrib chiqish imkoniyati tahrir oynasi tepasida boʻlsin",
-       "tog-previewonfirst": "Tahrirlashga oʻtgandayoq koʻrib chiqishni boshlash",
+       "tog-previewonfirst": "Tahrirlashga oʻtgandayoq koʻrib chiqish boshlansin",
        "tog-enotifwatchlistpages": "Kuzatuv roʻyxatimdagi sahifa yoki fayllar oʻzgartirilsa, menga bu haqda xat yuborilsin",
        "tog-enotifusertalkpages": "Munozara sahifam oʻzgartirilsa, menga bu haqda xat yuborilsin",
-       "tog-enotifminoredits": "Kichik tahrir qilinsa ham e-pochtamga bu haqda xat yuborilsin",
-       "tog-enotifrevealaddr": "Xabar beruvchi xatlarda e-pochta manzilim koʻrsatilsin",
-       "tog-shownumberswatching": "Sahifani kuzatuv roʻyxatiga olgan foydalanuvchilar sonini koʻrsatish",
-       "tog-oldsig": "Joriy imzo:",
+       "tog-enotifminoredits": "Sahifa va fayllarga kichik oʻzgarish kiritilsa ham elektron pochtamga bu haqda xat yuborilsin",
+       "tog-enotifrevealaddr": "Xabar beruvchi xatlarda elektron pochta manzilim koʻrsatilsin",
+       "tog-shownumberswatching": "Sahifani kuzatuv roʻyxatiga qoʻshgan foydalanuvchilar soni koʻrsatilsin",
+       "tog-oldsig": "Joriy imzoingiz:",
        "tog-fancysig": "Imzoni viki-belgi qilib koʻrsatish (avtomatik ishoratsiz)",
-       "tog-uselivepreview": "Tez koʻrib chiqish",
-       "tog-forceeditsummary": "Qisqa tavsif oynasi toʻldirilmagani haqida ogohlantirish koʻrsatish",
+       "tog-uselivepreview": "Oʻzgarishlarni sahifani yangilamasdan koʻrib chiqish",
+       "tog-forceeditsummary": "Qisqa tavsif oynasi toʻldirilmagani haqida ogohlantirish berilsin",
        "tog-watchlisthideown": "Oʻz tahrirlarim kuzatuv roʻyxatimda koʻrsatilmasin",
        "tog-watchlisthidebots": "Botlar qilgan tahrirlar kuzatuv roʻyxatimda koʻrsatilmasin",
        "tog-watchlisthideminor": "Kichik tahrirlar kuzatuv roʻyxatimda koʻrsatilmasin",
        "tog-watchlisthideliu": "Tizimga kirgan foydalanuvchilar tahrirlari kuzatuv roʻyxatimda koʻrsatilmasin",
-       "tog-watchlisthideanons": "Anonim foydalanuvchilar tahrirlari kuzatuv roʻyxatimda koʻrsatilmasin",
+       "tog-watchlisthideanons": "Anonimlarning tahrirlari kuzatuv roʻyxatimda koʻrsatilmasin",
        "tog-watchlisthidepatrolled": "Tekshirilgan tahrirlar kuzatuv roʻyxatimda koʻrsatilmasin",
+       "tog-watchlisthidecategorization": "Sahifalarning turkumlari yashirilsin",
        "tog-ccmeonemails": "Boshqa ishtirokchilarga yozgan xatimning nusxasi oʻzimga yuborilsin",
        "tog-diffonly": "Versiyalar taqqoslanayotganda, pastda sahifa matni koʻrsatilmasin",
-       "tog-showhiddencats": "Yashirin turkumlarni koʻrsatish",
-       "tog-norollbackdiff": "Tahrir qaytarilganda, versiyalar taqqosi koʻrsatilmasin",
-       "tog-useeditwarning": "Oʻzgarishlarni saqlamay sahifadan chiqib ketayotganim haqida ogohlantir",
-       "tog-prefershttps": "Doim himoyalangan holda kirish",
+       "tog-showhiddencats": "Yashirin turkumlar koʻrsatilsin",
+       "tog-norollbackdiff": "Biron tahrir tezda qaytarilsa, versiyalar taqqosini koʻrsatishning hojati yoʻq",
+       "tog-useeditwarning": "Oʻzgarishlarni saqlamay sahifadan chiqib ketayotganim haqida ogohlantirish berilsin",
+       "tog-prefershttps": "Tizimga kirganimdan keyin doim himoyalangan aloqadan foydalanilsin",
        "underline-always": "Har doim",
        "underline-never": "Hech qachon",
-       "underline-default": "Bezak mavzusi yoki brauzer andozasi boʻyicha",
+       "underline-default": "Brauzer sozlamalaridan foydalanilsin",
        "editfont-style": "Tahrirlash maydonidagi shrift turi:",
        "editfont-monospace": "Teng enli shrift (Monospaced)",
        "editfont-sansserif": "Kertiksiz shrift (Sans-serif)",
        "category_header": "„$1“ turkumidagi sahifalar",
        "subcategories": "Ostturkumlar",
        "category-media-header": "„$1“ turkumidagi fayllar",
-       "category-empty": "''Ushbu turkumda hozircha sahifa yoki fayllar yoʻq.''",
+       "category-empty": "<em>Ushbu turkumda hozircha sahifa yoki fayllar yoʻq.</em>",
        "hidden-categories": "{{PLURAL:$1|Yashirin turkum}}",
        "hidden-category-category": "Yashirin turkumlar",
        "category-subcat-count": "{{PLURAL:$2|Ushbu turkumda faqat bitta ostturkum mavjud.|Quyida ushbu turkumga kiruvchi $2 ta ostturkumdan $1 tasi koʻrsatilgan.}}",
        "mytalk": "Munozara",
        "anontalk": "Ushbu IP-manzil munozarasi",
        "navigation": "Saytda harakatlanish",
-       "and": "&nbsp;va",
+       "and": "&#32;va",
        "faq": "TSS",
        "actions": "Amallar",
        "namespaces": "Nomfazolar",
        "viewsourcelink": "manbasini koʻrish",
        "editsectionhint": "Boʻlimni tahrirlash: $1",
        "toc": "Mundarija",
-       "showtoc": "koʻrsatish",
+       "showtoc": "koʻrsat",
        "hidetoc": "yashirish",
        "collapsible-collapse": "Yigʻish",
        "collapsible-expand": "Yoyish",
        "viewsource-title": "$1 sahifasining manbasini koʻrish",
        "actionthrottled": "Tezlik cheklovi",
        "protectedpagetext": "Bu sahifa tahrirlash va boshqa oʻzgarishlar kiritishdan himoyalangan.",
-       "viewsourcetext": "Siz bu sahifaning manbasini koʻrishingiz va uni nusxasini olishingiz mumkin:",
+       "viewsourcetext": "Siz bu sahifaning ichki kodini koʻrishingiz va undan nusxa olishingiz mumkin:",
        "protectedinterface": "Ushbu sahifada dasturiy taʼminot interfeysi xabari mavjud. Bezoriliklardan saqlash uchun uni oʻzgartirish taʼqiqlangan.\nUshbu xabar tarjimasini qoʻshish yoki oʻzgartirish uchun, iltimos, MediaWikining [https://translatewiki.net/ translatewiki.net] mahalliylashtirish saytidan foydalaning.",
        "editinginterface": "<strong>Eʼtibor bering:</strong> Siz interfeys matnini aks ettiruvchi sahifani tahrirlamoqdasiz.\nUning oʻzgartirilishi boshqa foydalanuvchilar uchun ham interfeys oʻzgarishiga olib keladi.",
        "translateinterface": "Ushbu xabar tarjimasini qoʻshish yoki oʻzgartirish uchun, iltimos, MediaWikining [https://translatewiki.net/ translatewiki.net] mahalliylashtirish saytidan foydalaning.",
        "subject": "Mavzu/sarlavha",
        "minoredit": "Bu kichik tahrir",
        "watchthis": "Sahifani kuzatish",
-       "savearticle": "Saqla",
-       "publishpage": "Sahifani chop et",
-       "publishchanges": "Oʻzgarishlarni chop et",
+       "savearticle": "Chop etish",
+       "savechanges": "Oʻzgarishlarni saqlash",
+       "publishpage": "Sahifani chop etish",
+       "publishchanges": "Oʻzgarishlarni chop etish",
        "preview": "Ko‘rib chiqish",
        "showpreview": "Koʻrib chiqish",
        "showdiff": "Kiritilgan o‘zgarishlar",
        "rev-deleted-user": "(muallif nomi oʻchirilgan)",
        "rev-deleted-event": "(jurnal tafsilotlari o‘chirildi)",
        "rev-delundel": "koʻrsatish/yashirish",
-       "rev-showdeleted": "koʻrsatish",
+       "rev-showdeleted": "koʻrsat",
        "revdelete-show-file-submit": "Ha",
        "revdelete-confirm": "Iltimos, haqiqatdan ham shu harakatni amalga oshirmoqchiligingizni, uning oqibatlarini tushunib turganingizni va harakatingiz [[{{MediaWiki:Policy-url}}|qoidalarga]] asosanlanganini tasdiqlang.",
        "revdelete-hide-text": "Sahifaning ushbu versiyasi matnini yashirish",
        "prefs-signature": "Imzo",
        "prefs-dateformat": "Sana formati",
        "prefs-timeoffset": "Vaqt farqi",
-       "prefs-advancedediting": "Qoʻshimcha moslamalar",
+       "prefs-advancedediting": "Asosiy bandlar",
+       "prefs-editor": "Tahrirlagich",
        "prefs-advancedrc": "Qoʻshimcha moslamalar",
        "prefs-advancedrendering": "Qoʻshimcha moslamalar",
        "prefs-advancedsearchoptions": "Qoʻshimcha moslamalar",
        "userrights": "Huquqlarini oʻzgartirish",
        "userrights-lookup-user": "Foydalanuvchini tanlash",
        "userrights-user-editname": "Foydalanuvchi nomi:",
-       "editusergroup": "Shu foydalanuvchi huquqlarini oʻzgartirish",
+       "editusergroup": "Qaysi guruhlarga aʼzo ekanligini koʻrish",
        "editinguser": "{{GENDER:$1|Foydalanuvchi}} <strong>[[User:$1|$1]]</strong> $2 huquqlarini oʻzgartirish",
        "userrights-editusergroup": "Guruhlardagi aʼzoligini oʻzgartirish",
        "saveusergroups": "Oʻzgarishlarni saqlash",
        "newuserlogpage": "Foydalanuvchilarni roʻyxatga olish qaydlari",
        "newuserlogpagetext": "Yaqinda roʻyxatdan oʻtgan foydalanuvchilar roʻyxati",
        "rightslog": "Foydalanuvchi huquqlari koʻrsatilgan qaydlar",
-       "rightslogtext": "Foydalanuvchi huquqlarini oʻzgartirish qaydlari.",
+       "rightslogtext": "Bu sahifada foydalanuvchilarning huquqlarini oʻzgartirish qaydlari koʻrsatilgan.",
        "action-edit": "ushbu sahifani tahrirlash",
        "action-move": "bu sahifani koʻchirish",
        "action-move-subpages": "Bu sahifani va uning ostsahifalarini koʻchirish",
        "recentchanges-label-plusminus": "Sahifa vazni qanchaga oʻzgargani (bayt)",
        "recentchanges-legend-heading": "<strong>Izoh:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} ([[Special:NewPages|alohida roʻyxat]])",
+       "recentchanges-submit": "Koʻrsat",
+       "rcfilters-activefilters-show": "Koʻrsat",
        "rcnotefrom": "Quyida <strong>$3, $4</strong> dan keyin sodir boʻlgan oʻzgarishlar koʻrsatilgan (oxirgi <strong>$1</strong> tasi).",
        "rclistfrom": "$3, $2 dan keyin sodir boʻlgan oʻzgarishlarni koʻrsat",
        "rcshowhideminor": "Kichik tahrirlarni $1",
-       "rcshowhideminor-show": "koʻrsat",
+       "rcshowhideminor-show": "Koʻrsat",
        "rcshowhideminor-hide": "yashir",
        "rcshowhidebots": "Botlarni $1",
-       "rcshowhidebots-show": "koʻrsat",
+       "rcshowhidebots-show": "Koʻrsat",
        "rcshowhidebots-hide": "yashir",
        "rcshowhideliu": "Roʻyxatdan oʻtganlarni $1",
        "rcshowhideliu-show": "koʻrsat",
        "rcshowhideliu-hide": "yashir",
        "rcshowhideanons": "Anonimlarni $1",
-       "rcshowhideanons-show": "koʻrsat",
+       "rcshowhideanons-show": "Koʻrsat",
        "rcshowhideanons-hide": "yashir",
        "rcshowhidepatr": "Tekshirilgan tahrirlarni $1",
+       "rcshowhidepatr-show": "Koʻrsat",
        "rcshowhidepatr-hide": "yashir",
        "rcshowhidemine": "Oʻz tahrirlarimni $1",
-       "rcshowhidemine-show": "koʻrsat",
+       "rcshowhidemine-show": "Koʻrsat",
        "rcshowhidemine-hide": "yashir",
        "rclinks": "Oxirgi $2 kun ichida sodir boʻlgan $1 ta oʻzgarish koʻrsatildi",
        "diff": "farq",
        "hist": "tarix",
-       "hide": "yashir",
-       "show": "koʻrsat",
+       "hide": "Yashir",
+       "show": "Koʻrsat",
        "minoreditletter": "k",
        "newpageletter": "Y",
        "boteditletter": "b",
        "statistics-header-users": "Foydalanuvchilar statistikasi",
        "statistics-articles": "Maqolalar",
        "statistics-pages": "Sahifalar",
-       "statistics-pages-desc": "Ushbu vikidagi barcha sahifalar, jumladan munozara, yoʻnaltirish va hk.",
+       "statistics-pages-desc": "Ushbu vikidagi barcha sahifalar, shu jumladan munozara sahifalari, qayta yoʻnaltiruvchi va boshqa sahifalar",
        "statistics-files": "Yuklangan fayllar",
        "statistics-edits": "{{SITENAME}}dagi tahrirlarning umumiy soni",
        "statistics-edits-average": "Oʻrtacha tahrirlar soni (sahifa boshiga)",
        "pageswithprop-prop": "Xossa nomi:",
        "pageswithprop-submit": "Oʻtish",
        "brokenredirects-edit": "tahrirlash",
+       "withoutinterwiki-submit": "Koʻrsat",
        "nbytes": "$1 {{PLURAL:$1|bayt}}",
        "ncategories": "$1 {{PLURAL:$1|turkum|turkumlar}}",
        "nmembers": "$1 {{PLURAL:$1|ta sahifa}}",
        "wantedcategories": "Talab qilinayotgan turkumlar",
        "mostcategories": "Eng koʻp turkumli sahifalar",
        "prefixindex": "Prefiksli barcha sahifalar",
+       "prefixindex-submit": "Koʻrsat",
        "prefixindex-strip": "Natijalar roʻyxatida prefiks koʻrsatilmasin",
        "protectedpages": "Himoyalangan sahifalar",
        "listusers": "Foydalanuvchilar roʻyxati",
        "usercreated": "$1, $2 da {{GENDER:$3|roʻyxatdan oʻtgan}}",
        "newpages": "Yangi sahifalar",
+       "newpages-submit": "Koʻrsat",
        "move": "Ko‘chirish",
        "movethispage": "Bu sahifani koʻchirish",
        "pager-newer-n": "{{PLURAL:$1|yangiroq 1|yangiroq $1}}",
        "specialloguserlabel": "Ijrochi:",
        "speciallogtitlelabel": "Moʻljal:",
        "log": "Qaydlar",
+       "logeventslist-submit": "Koʻrsat",
        "all-logs-page": "Barcha ochiq qaydlar",
        "alllogstext": "{{SITENAME}}dagi barcha jurnallar roʻyxati.\nNatijalarni jurnal nomi, foydalanuvchi nomi (harflar katta-kichikligi inobatga olinadi) yoki sahifa nomi boʻyicha saralashingiz mumkin.\n* Biror foydalanuvchi amalga oshirgan qaydni topish uchun uning foydalanuvchi nomini „Ijrochi“ oynasiga kiriting.\n* Biror foydalanuvchi yoki sahifaga nisbatan amalga oshirilgan qaydni topish uchun ulardan birining nomini „Moʻljal“ oynasiga kiriting.",
        "logempty": "Talabga mos yozuvlar mavjud emas.",
        "allpages-hide-redirects": "Yoʻnaltirishlarni yashirish",
        "cachedspecial-refresh-now": "Oxirgi versiyasini koʻrish",
        "categories": "Turkumlar",
+       "categories-submit": "Koʻrsat",
        "categoriespagetext": "Quyidagi {{PLURAL:$1|turkumda|turkumlarda}} sahifa yoki media-fayllar mavjud.\n[[Special:UnusedCategories|Ishlatilmayotgan turkumlar]] bu yerda koʻrsatilmaydi.\nShuningdek qarang: [[Special:WantedCategories|talab qilinayotgan turkumlar]].",
        "categoriesfrom": "Quyidagidan boshlanuvchi turkumlarni koʻrsatish:",
        "deletedcontributions": "Foydalanuvchining o‘chirilgan hissasi",
        "linksearch-ok": "Qidirish",
        "linksearch-line": "$2 ichidan $1 ga havola",
        "listusersfrom": "Quyidagidan boshlanuvchi foydalanuvchilarni koʻrsatish:",
-       "listusers-submit": "Koʻrsatish",
+       "listusers-submit": "Koʻrsat",
        "listusers-noresult": "Foydalanuvchilar topilmadi.",
        "listusers-blocked": "(chetlashtirilgan)",
        "activeusers": "Faol foydalanuvchilar roʻyxati",
        "wlheader-showupdated": "Siz oxirgi marta kirganingizdan keyin oʻzgartirilgan sahifalar '''qalin''' yozuv bilan ajratib koʻrsatilgan.",
        "wlnote": "Quyida oxirgi $2 soat ichida sodir boʻlgan {{PLURAL:$1|oxirgi oʻzgarish|<strong>$1</strong> ta oʻzgarishlar}} koʻrsatilgan. $3, $4.",
        "wlshowlast": "Oxirgi $1 soatdagi $2 kundagi tahrirlarni koʻrsatish",
+       "watchlist-submit": "Koʻrsat",
        "watchlist-options": "Kuzatuv roʻyxati moslamalari",
        "watching": "Kuzatish...",
        "unwatching": "Kuzatuv roʻyxatidan oʻchirilmoqda...",
        "delete-confirm": "$1 — oʻchirish",
        "delete-legend": "Sahifani o‘chirish",
        "historywarning": "<strong>Diqqat:</strong> Siz oʻchirmoqchi boʻlayotgan sahifaning tarixida $1 ta {{PLURAL:$1|versiyasi}} bor:",
+       "historyaction-submit": "Koʻrsat",
        "confirmdeletetext": "Siz ushbu sahifani va uning tarixini butunlay oʻchirib tashlamoqchi boʻlyapsiz. Iltimos, [[Special:Whatlinkshere/{{FULLPAGENAMEE}}|bogʻlangan sahifalar]] bilan tanishib chiqishni unutmang.",
        "actioncomplete": "Bajarildi",
        "actionfailed": "Jarayon amalga oshmadi",
        "contributions-title": "{{GENDER:$1|Foydalanuvchi}} $1 hissasi",
        "mycontris": "Hissam",
        "anoncontribs": "Qoʻshilgan hissa",
-       "contribsub2": "$1 uchun ($2)",
+       "contribsub2": "$1ning tahrirlari ($2)",
        "nocontribs": "Belgilangan shartlarga muvofiq oʻzgarishlar topilmadi.",
        "uctop": "joriy",
        "month": "Oydan (va avvalroq)",
        "year": "Yildan (va avvalroq)",
+       "date": "Shu sanadan avvalroq:",
        "sp-contributions-newbies": "Faqatgina yangi foydalanuvchilarning hissalarini koʻrsat",
        "sp-contributions-newbies-sub": "Yangi hisob yozuvlaridan",
        "sp-contributions-newbies-title": "Yangi hisob yozuvlarining hissalari",
        "tooltip-ca-nstab-category": "Turkum sahifasini koʻrish",
        "tooltip-minoredit": "Kichik o‘zgartirish sifatida belgilash",
        "tooltip-save": "Oʻzgarishlarni saqlash",
+       "tooltip-publish": "Siz kiritgan oʻzgarishlarni chop etish",
        "tooltip-preview": "Oʻzgarishlarni koʻrib chiqish; Iltimos, saqlashdan oldin undan foydalaning!",
        "tooltip-diff": "Matnga qanday oʻzgarishlar kiritganligingizni koʻrish.",
        "tooltip-compareselectedversions": "Bu sahifaning ikki tanlangan versiyalari orasidagi farqni koʻrish.",
        "watchlisttools-raw": "Kuzatuv roʻyxatimni tahrirlash",
        "signature": "[[{{ns:user}}:$1|$2]] ([[{{ns:user_talk}}:$1|munozara]])",
        "duplicate-defaultsort": "'''Diqqat:''' \"$2\" boshlang'ich saralash kaliti oldingi \"$1\" boshlang'ich saralash kalitini qayta aniqlayapti.",
+       "version": "Versiyasi",
        "version-specialpages": "Maxsus sahifalar",
+       "version-ext-colheader-version": "Versiyasi",
+       "version-software-version": "Versiyasi",
+       "version-libraries-version": "Versiyasi",
        "specialpages": "Maxsus sahifalar",
        "tag-filter": "[[Special:Tags|Nishonlar]] filtri:",
        "tag-list-wrapper": "[[Special:Tags|{{PLURAL:$1|Teg|Teglar}}]]: $2",
        "special-characters-group-khmer": "Kxmer",
        "special-characters-title-emdash": "uzun tire",
        "special-characters-title-minus": "minus belgisi",
-       "mw-widgets-abandonedit": "Siz haqiqatdan ham oʻzgarishlarni saqlamasdan koʻrish tartibiga oʻtishni xohlaysizmi?"
+       "mw-widgets-abandonedit": "Siz haqiqatdan ham oʻzgarishlarni saqlamasdan koʻrish tartibiga oʻtishni xohlaysizmi?",
+       "log-action-filter-block": "Chetlatish turi:",
+       "log-action-filter-rights": "Huquqlarni oʻzgartirish turi:",
+       "log-action-filter-all": "Barchasi",
+       "log-action-filter-block-block": "Chetlatish",
+       "log-action-filter-block-reblock": "Chetlatish turini oʻzgartirish",
+       "log-action-filter-block-unblock": "Chetlatishni bekor qilish",
+       "log-action-filter-rights-rights": "Qoʻlda kiritilgan oʻzgarish",
+       "authprovider-resetpass-skip-label": "Qoldirib ketish"
 }
index 296caa7..0a86677 100644 (file)
        "booksources-search": "זוכן",
        "booksources-text": "אונטן איז א ליסטע פון סייטס וואס פֿארקויפֿן נייע און גענוצטע ביכער און האבן אויך נאך אינפֿארמאציע וועגן די ביכער וואס איר זוכט:",
        "booksources-invalid-isbn": "דאָס געגעבענע ISBN זעט נישט אויס צו זיין גילטיק; קאנטראלירט פֿאַר גרײַזן בײַם קאפּירן פון דעם ערשטיקן מקור.",
+       "magiclink-tracking-rfc": "בלעטער וואס ניצן מאגישע RFC-לינקען",
+       "magiclink-tracking-pmid": "בלעטער וואס ניצן מאגישע PMID-לינקען",
+       "magiclink-tracking-isbn": "בלעטער וואס ניצן מאגישע ISBN-לינקען",
        "specialloguserlabel": "אויספֿירער:",
        "speciallogtitlelabel": "ציל (טיטל אדער {{ns:user}}:באניצער־נאמען פאר א באניצער):",
        "log": "לאגביכער",
index 30ef496..fb02c34 100644 (file)
        "edit-gone-missing": "不能更新页面。\n它可能刚刚被删除。",
        "edit-conflict": "编辑冲突。",
        "edit-no-change": "因为没有文字更改,您的编辑已被忽略。",
+       "edit-slots-cannot-add": "下列{{PLURAL:$1|栏位|栏位}}在此不受支持:$2。",
+       "edit-slots-cannot-remove": "下列{{PLURAL:$1|栏位|栏位}}是必需的且无法被移除:$2。",
        "postedit-confirmation-created": "页面已创建。",
        "postedit-confirmation-restored": "页面已恢复。",
        "postedit-confirmation-saved": "您的编辑已保存。",
index ee5b8c3..4dccd14 100644 (file)
@@ -146,11 +146,11 @@ qunitjs:
   # Integrity from link modals at https://code.jquery.com/qunit/
   files:
     qunit.js:
-      src: https://code.jquery.com/qunit/qunit-2.6.2.js
-      integrity: sha256-72OhbBvECs6Z5vG0GfPqiyYvTf8vhdEVHKQcacIcIeM=
+      src: http://code.jquery.com/qunit/qunit-2.9.1.js
+      integrity: sha256-eNccBdxd8zReziWcVjEsPeyJDi3LKMYnzMXyDv8bzsU=
     qunit.css:
-      src: https://code.jquery.com/qunit/qunit-2.6.2.css
-      integrity: sha256-qpkurjTvVTJJCSpMABcvF4IlYUJkd8saxiHgUQpEjX8=
+      src: https://code.jquery.com/qunit/qunit-2.9.1.css
+      integrity: sha256-SSS7o92V7wzcIFg3qnJL9mc4msePaT4klbxtuSGvVVo=
 
 sinonjs:
   type: file
index 74bdcf8..b0c5c4c 100644 (file)
@@ -18,7 +18,9 @@
                $( '.config-help-field-data' ).hide()
                        .closest( '.config-help-field-container' ).find( '.config-help-field-hint' )
                        .show()
-                       .click( function () {
+                       .on( 'click', function () {
+                               // FIXME: Use CSS transition
+                               // eslint-disable-next-line jquery/no-slide
                                $( this ).closest( '.config-help-field-container' ).find( '.config-help-field-data' )
                                        .slideToggle( 'fast' );
                        } );
                        $( document.getElementById( $( this ).attr( 'rel' ) ) ).hide();
                } );
                $( document.getElementById( $( '.dbRadio:checked' ).attr( 'rel' ) ) ).show();
-               $( '.dbRadio' ).click( function () {
+               $( '.dbRadio' ).on( 'click', function () {
                        var $checked = $( '.dbRadio:checked' ),
                                $wrapper = $( document.getElementById( $checked.attr( 'rel' ) ) );
                        if ( $wrapper.is( ':hidden' ) ) {
+                               // FIXME: Use CSS transition
+                               // eslint-disable-next-line jquery/no-animate-toggle
                                $( '.dbWrapper' ).hide( 'slow' );
+                               // eslint-disable-next-line jquery/no-animate-toggle
                                $wrapper.show( 'slow' );
                        }
                } );
                } );
 
                // Show/hide Creative Commons thingy
-               $( '.licenseRadio' ).click( function () {
+               $( '.licenseRadio' ).on( 'click', function () {
                        var $wrapper = $( '#config-cc-wrapper' );
                        if ( $( '#config__LicenseCode_cc-choose' ).is( ':checked' ) ) {
+                               // FIXME: Use CSS transition
+                               // eslint-disable-next-line jquery/no-animate-toggle
                                $wrapper.show( 'slow' );
                        } else {
+                               // eslint-disable-next-line jquery/no-animate-toggle
                                $wrapper.hide( 'slow' );
                        }
                } );
 
                // Show/hide random stuff (email, upload)
-               $( '.showHideRadio' ).click( function () {
+               $( '.showHideRadio' ).on( 'click', function () {
                        var $wrapper = $( '#' + $( this ).attr( 'rel' ) );
                        if ( $( this ).is( ':checked' ) ) {
+                               // FIXME: Use CSS transition
+                               // eslint-disable-next-line jquery/no-animate-toggle
                                $wrapper.show( 'slow' );
                        } else {
+                               // eslint-disable-next-line jquery/no-animate-toggle
                                $wrapper.hide( 'slow' );
                        }
                } );
-               $( '.hideShowRadio' ).click( function () {
+               $( '.hideShowRadio' ).on( 'click', function () {
                        var $wrapper = $( '#' + $( this ).attr( 'rel' ) );
                        if ( $( this ).is( ':checked' ) ) {
+                               // FIXME: Use CSS transition
+                               // eslint-disable-next-line jquery/no-animate-toggle
                                $wrapper.hide( 'slow' );
                        } else {
+                               // eslint-disable-next-line jquery/no-animate-toggle
                                $wrapper.show( 'slow' );
                        }
                } );
                }
 
                // Enable/disable "other" textboxes
-               $( '.enableForOther' ).click( function () {
+               $( '.enableForOther' ).on( 'click', function () {
                        var $textbox = $( document.getElementById( $( this ).attr( 'rel' ) ) );
                        // FIXME: Ugh, this is ugly
                        if ( $( this ).val() === 'other' ) {
+                               // FIXME: Use CSS transition
+                               // eslint-disable-next-line jquery/no-slide
                                $textbox.prop( 'readonly', false ).closest( '.config-block' ).slideDown( 'fast' );
                        } else {
+                               // eslint-disable-next-line jquery/no-slide
                                $textbox.prop( 'readonly', true ).closest( '.config-block' ).slideUp( 'fast' );
                        }
                } );
                $( '#config_wgSitename' ).on( 'keyup change', syncText ).each( syncText );
 
                // Show/Hide memcached servers when needed
-               $( 'input[name$="config__MainCacheType"]' ).change( function () {
+               $( 'input[name$="config__MainCacheType"]' ).on( 'change', function () {
                        var $memc = $( '#config-memcachewrapper' );
                        if ( $( 'input[name$="config__MainCacheType"]:checked' ).val() === 'memcached' ) {
+                               // FIXME: Use CSS transition
+                               // eslint-disable-next-line jquery/no-animate-toggle
                                $memc.show( 'slow' );
                        } else {
+                               // eslint-disable-next-line jquery/no-animate-toggle
                                $memc.hide( 'slow' );
                        }
                } );
index e3379ca..03be1e5 100644 (file)
@@ -11,7 +11,7 @@
   },
   "devDependencies": {
     "deepmerge": "1.3.2",
-    "eslint-config-wikimedia": "0.9.0",
+    "eslint-config-wikimedia": "0.10.0",
     "grunt": "1.0.3",
     "grunt-banana-checker": "0.6.0",
     "grunt-contrib-copy": "1.0.0",
@@ -26,7 +26,7 @@
     "karma-mocha-reporter": "2.2.5",
     "karma-qunit": "2.1.0",
     "postcss-less": "2.0.0",
-    "qunit": "2.6.2",
+    "qunit": "2.9.1",
     "stylelint": "9.6.0",
     "stylelint-config-wikimedia": "0.5.0",
     "wdio-junit-reporter": "0.2.0",
index 767f6de..6589e75 100644 (file)
@@ -1987,7 +1987,7 @@ return [
                        'resources/src/mediawiki.special/upload.css',
                        'resources/src/mediawiki.special/userrights.css',
                        'resources/src/mediawiki.special/watchlist.css',
-                       'resources/src/mediawiki.special/block.less'
+                       'resources/src/mediawiki.special/block.less',
                ],
                'targets' => [ 'desktop', 'mobile' ],
        ],
index a7002a0..4e99a39 100644 (file)
@@ -1,12 +1,12 @@
 /*!
- * QUnit 2.6.2
+ * QUnit 2.9.1
  * https://qunitjs.com/
  *
  * Copyright jQuery Foundation and other contributors
  * Released under the MIT license
  * https://jquery.org/license
  *
- * Date: 2018-08-19T19:37Z
+ * Date: 2019-01-07T16:37Z
  */
 
 /** Font Family and Sizes */
index aea68ba..99f80dc 100644 (file)
@@ -1,26 +1,26 @@
 /*!
- * QUnit 2.6.2
+ * QUnit 2.9.1
  * https://qunitjs.com/
  *
  * Copyright jQuery Foundation and other contributors
  * Released under the MIT license
  * https://jquery.org/license
  *
- * Date: 2018-08-19T19:37Z
+ * Date: 2019-01-07T16:37Z
  */
 (function (global$1) {
   'use strict';
 
   global$1 = global$1 && global$1.hasOwnProperty('default') ? global$1['default'] : global$1;
 
-  var window = global$1.window;
+  var window$1 = global$1.window;
   var self$1 = global$1.self;
   var console = global$1.console;
-  var setTimeout = global$1.setTimeout;
+  var setTimeout$1 = global$1.setTimeout;
   var clearTimeout = global$1.clearTimeout;
 
-  var document = window && window.document;
-  var navigator = window && window.navigator;
+  var document$1 = window$1 && window$1.document;
+  var navigator = window$1 && window$1.navigator;
 
   var localSessionStorage = function () {
        var x = "qunit-test-string";
        }
   }();
 
+  /**
+   * Returns a function that proxies to the given method name on the globals
+   * console object. The proxy will also detect if the console doesn't exist and
+   * will appropriately no-op. This allows support for IE9, which doesn't have a
+   * console if the developer tools are not open.
+   */
+  function consoleProxy(method) {
+       return function () {
+               if (console) {
+                       console[method].apply(console, arguments);
+               }
+       };
+  }
+
+  var Logger = {
+       warn: consoleProxy("warn")
+  };
+
   var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
     return typeof obj;
   } : function (obj) {
        return new Date().getTime();
   };
 
+  var hasPerformanceApi = detectPerformanceApi();
+  var performance = hasPerformanceApi ? window$1.performance : undefined;
+  var performanceNow = hasPerformanceApi ? performance.now.bind(performance) : now;
+
+  function detectPerformanceApi() {
+       return window$1 && typeof window$1.performance !== "undefined" && typeof window$1.performance.mark === "function" && typeof window$1.performance.measure === "function";
+  }
+
+  function measure(comment, startMark, endMark) {
+
+       // `performance.measure` may fail if the mark could not be found.
+       // reasons a specific mark could not be found include: outside code invoking `performance.clearMarks()`
+       try {
+               performance.measure(comment, startMark, endMark);
+       } catch (ex) {
+               Logger.warn("performance.measure could not be executed because of ", ex.message);
+       }
+  }
+
   var defined = {
-       document: window && window.document !== undefined,
-       setTimeout: setTimeout !== undefined
+       document: window$1 && window$1.document !== undefined,
+       setTimeout: setTimeout$1 !== undefined
   };
 
   // Returns a new Array with the elements that are in a but not in b
   };
 
   // take a predefined QUnit.config and extend the defaults
-  var globalConfig = window && window.QUnit && window.QUnit.config;
+  var globalConfig = window$1 && window$1.QUnit && window$1.QUnit.config;
 
   // only extend the global config if there is no QUnit overload
-  if (window && window.QUnit && !window.QUnit.version) {
+  if (window$1 && window$1.QUnit && !window$1.QUnit.version) {
        extend(config, globalConfig);
   }
 
                key: "start",
                value: function start(recordTime) {
                        if (recordTime) {
-                               this._startTime = Date.now();
+                               this._startTime = performanceNow();
+
+                               if (performance) {
+                                       var suiteLevel = this.fullName.length;
+                                       performance.mark("qunit_suite_" + suiteLevel + "_start");
+                               }
                        }
 
                        return {
                key: "end",
                value: function end(recordTime) {
                        if (recordTime) {
-                               this._endTime = Date.now();
+                               this._endTime = performanceNow();
+
+                               if (performance) {
+                                       var suiteLevel = this.fullName.length;
+                                       performance.mark("qunit_suite_" + suiteLevel + "_end");
+
+                                       var suiteName = this.fullName.join(" – ");
+
+                                       measure(suiteLevel === 0 ? "QUnit Test Run" : "QUnit Test Suite: " + suiteName, "qunit_suite_" + suiteLevel + "_start", "qunit_suite_" + suiteLevel + "_end");
+                               }
                        }
 
                        return {
        }
   }
 
+  function objectOrFunction(x) {
+    var type = typeof x === 'undefined' ? 'undefined' : _typeof(x);
+    return x !== null && (type === 'object' || type === 'function');
+  }
+
+  function isFunction(x) {
+    return typeof x === 'function';
+  }
+
+
+
+  var _isArray = void 0;
+  if (Array.isArray) {
+    _isArray = Array.isArray;
+  } else {
+    _isArray = function _isArray(x) {
+      return Object.prototype.toString.call(x) === '[object Array]';
+    };
+  }
+
+  var isArray = _isArray;
+
+  var len = 0;
+  var vertxNext = void 0;
+  var customSchedulerFn = void 0;
+
+  var asap = function asap(callback, arg) {
+    queue[len] = callback;
+    queue[len + 1] = arg;
+    len += 2;
+    if (len === 2) {
+      // If len is 2, that means that we need to schedule an async flush.
+      // If additional callbacks are queued before the queue is flushed, they
+      // will be processed by this flush that we are scheduling.
+      if (customSchedulerFn) {
+        customSchedulerFn(flush);
+      } else {
+        scheduleFlush();
+      }
+    }
+  };
+
+  function setScheduler(scheduleFn) {
+    customSchedulerFn = scheduleFn;
+  }
+
+  function setAsap(asapFn) {
+    asap = asapFn;
+  }
+
+  var browserWindow = typeof window !== 'undefined' ? window : undefined;
+  var browserGlobal = browserWindow || {};
+  var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
+  var isNode = typeof self === 'undefined' && typeof process !== 'undefined' && {}.toString.call(process) === '[object process]';
+
+  // test for web worker but not in IE10
+  var isWorker = typeof Uint8ClampedArray !== 'undefined' && typeof importScripts !== 'undefined' && typeof MessageChannel !== 'undefined';
+
+  // node
+  function useNextTick() {
+    // node version 0.10.x displays a deprecation warning when nextTick is used recursively
+    // see https://github.com/cujojs/when/issues/410 for details
+    return function () {
+      return process.nextTick(flush);
+    };
+  }
+
+  // vertx
+  function useVertxTimer() {
+    if (typeof vertxNext !== 'undefined') {
+      return function () {
+        vertxNext(flush);
+      };
+    }
+
+    return useSetTimeout();
+  }
+
+  function useMutationObserver() {
+    var iterations = 0;
+    var observer = new BrowserMutationObserver(flush);
+    var node = document.createTextNode('');
+    observer.observe(node, { characterData: true });
+
+    return function () {
+      node.data = iterations = ++iterations % 2;
+    };
+  }
+
+  // web worker
+  function useMessageChannel() {
+    var channel = new MessageChannel();
+    channel.port1.onmessage = flush;
+    return function () {
+      return channel.port2.postMessage(0);
+    };
+  }
+
+  function useSetTimeout() {
+    // Store setTimeout reference so es6-promise will be unaffected by
+    // other code modifying setTimeout (like sinon.useFakeTimers())
+    var globalSetTimeout = setTimeout;
+    return function () {
+      return globalSetTimeout(flush, 1);
+    };
+  }
+
+  var queue = new Array(1000);
+  function flush() {
+    for (var i = 0; i < len; i += 2) {
+      var callback = queue[i];
+      var arg = queue[i + 1];
+
+      callback(arg);
+
+      queue[i] = undefined;
+      queue[i + 1] = undefined;
+    }
+
+    len = 0;
+  }
+
+  function attemptVertx() {
+    try {
+      var vertx = Function('return this')().require('vertx');
+      vertxNext = vertx.runOnLoop || vertx.runOnContext;
+      return useVertxTimer();
+    } catch (e) {
+      return useSetTimeout();
+    }
+  }
+
+  var scheduleFlush = void 0;
+  // Decide what async method to use to triggering processing of queued callbacks:
+  if (isNode) {
+    scheduleFlush = useNextTick();
+  } else if (BrowserMutationObserver) {
+    scheduleFlush = useMutationObserver();
+  } else if (isWorker) {
+    scheduleFlush = useMessageChannel();
+  } else if (browserWindow === undefined && typeof require === 'function') {
+    scheduleFlush = attemptVertx();
+  } else {
+    scheduleFlush = useSetTimeout();
+  }
+
+  function then(onFulfillment, onRejection) {
+    var parent = this;
+
+    var child = new this.constructor(noop);
+
+    if (child[PROMISE_ID] === undefined) {
+      makePromise(child);
+    }
+
+    var _state = parent._state;
+
+
+    if (_state) {
+      var callback = arguments[_state - 1];
+      asap(function () {
+        return invokeCallback(_state, child, callback, parent._result);
+      });
+    } else {
+      subscribe(parent, child, onFulfillment, onRejection);
+    }
+
+    return child;
+  }
+
+  /**
+    `Promise.resolve` returns a promise that will become resolved with the
+    passed `value`. It is shorthand for the following:
+
+    ```javascript
+    let promise = new Promise(function(resolve, reject){
+      resolve(1);
+    });
+
+    promise.then(function(value){
+      // value === 1
+    });
+    ```
+
+    Instead of writing the above, your code now simply becomes the following:
+
+    ```javascript
+    let promise = Promise.resolve(1);
+
+    promise.then(function(value){
+      // value === 1
+    });
+    ```
+
+    @method resolve
+    @static
+    @param {Any} value value that the returned promise will be resolved with
+    Useful for tooling.
+    @return {Promise} a promise that will become fulfilled with the given
+    `value`
+  */
+  function resolve$1(object) {
+    /*jshint validthis:true */
+    var Constructor = this;
+
+    if (object && (typeof object === 'undefined' ? 'undefined' : _typeof(object)) === 'object' && object.constructor === Constructor) {
+      return object;
+    }
+
+    var promise = new Constructor(noop);
+    resolve(promise, object);
+    return promise;
+  }
+
+  var PROMISE_ID = Math.random().toString(36).substring(2);
+
+  function noop() {}
+
+  var PENDING = void 0;
+  var FULFILLED = 1;
+  var REJECTED = 2;
+
+  var TRY_CATCH_ERROR = { error: null };
+
+  function selfFulfillment() {
+    return new TypeError("You cannot resolve a promise with itself");
+  }
+
+  function cannotReturnOwn() {
+    return new TypeError('A promises callback cannot return that same promise.');
+  }
+
+  function getThen(promise) {
+    try {
+      return promise.then;
+    } catch (error) {
+      TRY_CATCH_ERROR.error = error;
+      return TRY_CATCH_ERROR;
+    }
+  }
+
+  function tryThen(then$$1, value, fulfillmentHandler, rejectionHandler) {
+    try {
+      then$$1.call(value, fulfillmentHandler, rejectionHandler);
+    } catch (e) {
+      return e;
+    }
+  }
+
+  function handleForeignThenable(promise, thenable, then$$1) {
+    asap(function (promise) {
+      var sealed = false;
+      var error = tryThen(then$$1, thenable, function (value) {
+        if (sealed) {
+          return;
+        }
+        sealed = true;
+        if (thenable !== value) {
+          resolve(promise, value);
+        } else {
+          fulfill(promise, value);
+        }
+      }, function (reason) {
+        if (sealed) {
+          return;
+        }
+        sealed = true;
+
+        reject(promise, reason);
+      }, 'Settle: ' + (promise._label || ' unknown promise'));
+
+      if (!sealed && error) {
+        sealed = true;
+        reject(promise, error);
+      }
+    }, promise);
+  }
+
+  function handleOwnThenable(promise, thenable) {
+    if (thenable._state === FULFILLED) {
+      fulfill(promise, thenable._result);
+    } else if (thenable._state === REJECTED) {
+      reject(promise, thenable._result);
+    } else {
+      subscribe(thenable, undefined, function (value) {
+        return resolve(promise, value);
+      }, function (reason) {
+        return reject(promise, reason);
+      });
+    }
+  }
+
+  function handleMaybeThenable(promise, maybeThenable, then$$1) {
+    if (maybeThenable.constructor === promise.constructor && then$$1 === then && maybeThenable.constructor.resolve === resolve$1) {
+      handleOwnThenable(promise, maybeThenable);
+    } else {
+      if (then$$1 === TRY_CATCH_ERROR) {
+        reject(promise, TRY_CATCH_ERROR.error);
+        TRY_CATCH_ERROR.error = null;
+      } else if (then$$1 === undefined) {
+        fulfill(promise, maybeThenable);
+      } else if (isFunction(then$$1)) {
+        handleForeignThenable(promise, maybeThenable, then$$1);
+      } else {
+        fulfill(promise, maybeThenable);
+      }
+    }
+  }
+
+  function resolve(promise, value) {
+    if (promise === value) {
+      reject(promise, selfFulfillment());
+    } else if (objectOrFunction(value)) {
+      handleMaybeThenable(promise, value, getThen(value));
+    } else {
+      fulfill(promise, value);
+    }
+  }
+
+  function publishRejection(promise) {
+    if (promise._onerror) {
+      promise._onerror(promise._result);
+    }
+
+    publish(promise);
+  }
+
+  function fulfill(promise, value) {
+    if (promise._state !== PENDING) {
+      return;
+    }
+
+    promise._result = value;
+    promise._state = FULFILLED;
+
+    if (promise._subscribers.length !== 0) {
+      asap(publish, promise);
+    }
+  }
+
+  function reject(promise, reason) {
+    if (promise._state !== PENDING) {
+      return;
+    }
+    promise._state = REJECTED;
+    promise._result = reason;
+
+    asap(publishRejection, promise);
+  }
+
+  function subscribe(parent, child, onFulfillment, onRejection) {
+    var _subscribers = parent._subscribers;
+    var length = _subscribers.length;
+
+
+    parent._onerror = null;
+
+    _subscribers[length] = child;
+    _subscribers[length + FULFILLED] = onFulfillment;
+    _subscribers[length + REJECTED] = onRejection;
+
+    if (length === 0 && parent._state) {
+      asap(publish, parent);
+    }
+  }
+
+  function publish(promise) {
+    var subscribers = promise._subscribers;
+    var settled = promise._state;
+
+    if (subscribers.length === 0) {
+      return;
+    }
+
+    var child = void 0,
+        callback = void 0,
+        detail = promise._result;
+
+    for (var i = 0; i < subscribers.length; i += 3) {
+      child = subscribers[i];
+      callback = subscribers[i + settled];
+
+      if (child) {
+        invokeCallback(settled, child, callback, detail);
+      } else {
+        callback(detail);
+      }
+    }
+
+    promise._subscribers.length = 0;
+  }
+
+  function tryCatch(callback, detail) {
+    try {
+      return callback(detail);
+    } catch (e) {
+      TRY_CATCH_ERROR.error = e;
+      return TRY_CATCH_ERROR;
+    }
+  }
+
+  function invokeCallback(settled, promise, callback, detail) {
+    var hasCallback = isFunction(callback),
+        value = void 0,
+        error = void 0,
+        succeeded = void 0,
+        failed = void 0;
+
+    if (hasCallback) {
+      value = tryCatch(callback, detail);
+
+      if (value === TRY_CATCH_ERROR) {
+        failed = true;
+        error = value.error;
+        value.error = null;
+      } else {
+        succeeded = true;
+      }
+
+      if (promise === value) {
+        reject(promise, cannotReturnOwn());
+        return;
+      }
+    } else {
+      value = detail;
+      succeeded = true;
+    }
+
+    if (promise._state !== PENDING) {
+      // noop
+    } else if (hasCallback && succeeded) {
+      resolve(promise, value);
+    } else if (failed) {
+      reject(promise, error);
+    } else if (settled === FULFILLED) {
+      fulfill(promise, value);
+    } else if (settled === REJECTED) {
+      reject(promise, value);
+    }
+  }
+
+  function initializePromise(promise, resolver) {
+    try {
+      resolver(function resolvePromise(value) {
+        resolve(promise, value);
+      }, function rejectPromise(reason) {
+        reject(promise, reason);
+      });
+    } catch (e) {
+      reject(promise, e);
+    }
+  }
+
+  var id = 0;
+  function nextId() {
+    return id++;
+  }
+
+  function makePromise(promise) {
+    promise[PROMISE_ID] = id++;
+    promise._state = undefined;
+    promise._result = undefined;
+    promise._subscribers = [];
+  }
+
+  function validationError() {
+    return new Error('Array Methods must be provided an Array');
+  }
+
+  var Enumerator = function () {
+    function Enumerator(Constructor, input) {
+      classCallCheck(this, Enumerator);
+
+      this._instanceConstructor = Constructor;
+      this.promise = new Constructor(noop);
+
+      if (!this.promise[PROMISE_ID]) {
+        makePromise(this.promise);
+      }
+
+      if (isArray(input)) {
+        this.length = input.length;
+        this._remaining = input.length;
+
+        this._result = new Array(this.length);
+
+        if (this.length === 0) {
+          fulfill(this.promise, this._result);
+        } else {
+          this.length = this.length || 0;
+          this._enumerate(input);
+          if (this._remaining === 0) {
+            fulfill(this.promise, this._result);
+          }
+        }
+      } else {
+        reject(this.promise, validationError());
+      }
+    }
+
+    createClass(Enumerator, [{
+      key: '_enumerate',
+      value: function _enumerate(input) {
+        for (var i = 0; this._state === PENDING && i < input.length; i++) {
+          this._eachEntry(input[i], i);
+        }
+      }
+    }, {
+      key: '_eachEntry',
+      value: function _eachEntry(entry, i) {
+        var c = this._instanceConstructor;
+        var resolve$$1 = c.resolve;
+
+
+        if (resolve$$1 === resolve$1) {
+          var _then = getThen(entry);
+
+          if (_then === then && entry._state !== PENDING) {
+            this._settledAt(entry._state, i, entry._result);
+          } else if (typeof _then !== 'function') {
+            this._remaining--;
+            this._result[i] = entry;
+          } else if (c === Promise$2) {
+            var promise = new c(noop);
+            handleMaybeThenable(promise, entry, _then);
+            this._willSettleAt(promise, i);
+          } else {
+            this._willSettleAt(new c(function (resolve$$1) {
+              return resolve$$1(entry);
+            }), i);
+          }
+        } else {
+          this._willSettleAt(resolve$$1(entry), i);
+        }
+      }
+    }, {
+      key: '_settledAt',
+      value: function _settledAt(state, i, value) {
+        var promise = this.promise;
+
+
+        if (promise._state === PENDING) {
+          this._remaining--;
+
+          if (state === REJECTED) {
+            reject(promise, value);
+          } else {
+            this._result[i] = value;
+          }
+        }
+
+        if (this._remaining === 0) {
+          fulfill(promise, this._result);
+        }
+      }
+    }, {
+      key: '_willSettleAt',
+      value: function _willSettleAt(promise, i) {
+        var enumerator = this;
+
+        subscribe(promise, undefined, function (value) {
+          return enumerator._settledAt(FULFILLED, i, value);
+        }, function (reason) {
+          return enumerator._settledAt(REJECTED, i, reason);
+        });
+      }
+    }]);
+    return Enumerator;
+  }();
+
+  /**
+    `Promise.all` accepts an array of promises, and returns a new promise which
+    is fulfilled with an array of fulfillment values for the passed promises, or
+    rejected with the reason of the first passed promise to be rejected. It casts all
+    elements of the passed iterable to promises as it runs this algorithm.
+
+    Example:
+
+    ```javascript
+    let promise1 = resolve(1);
+    let promise2 = resolve(2);
+    let promise3 = resolve(3);
+    let promises = [ promise1, promise2, promise3 ];
+
+    Promise.all(promises).then(function(array){
+      // The array here would be [ 1, 2, 3 ];
+    });
+    ```
+
+    If any of the `promises` given to `all` are rejected, the first promise
+    that is rejected will be given as an argument to the returned promises's
+    rejection handler. For example:
+
+    Example:
+
+    ```javascript
+    let promise1 = resolve(1);
+    let promise2 = reject(new Error("2"));
+    let promise3 = reject(new Error("3"));
+    let promises = [ promise1, promise2, promise3 ];
+
+    Promise.all(promises).then(function(array){
+      // Code here never runs because there are rejected promises!
+    }, function(error) {
+      // error.message === "2"
+    });
+    ```
+
+    @method all
+    @static
+    @param {Array} entries array of promises
+    @param {String} label optional string for labeling the promise.
+    Useful for tooling.
+    @return {Promise} promise that is fulfilled when all `promises` have been
+    fulfilled, or rejected if any of them become rejected.
+    @static
+  */
+  function all(entries) {
+    return new Enumerator(this, entries).promise;
+  }
+
+  /**
+    `Promise.race` returns a new promise which is settled in the same way as the
+    first passed promise to settle.
+
+    Example:
+
+    ```javascript
+    let promise1 = new Promise(function(resolve, reject){
+      setTimeout(function(){
+        resolve('promise 1');
+      }, 200);
+    });
+
+    let promise2 = new Promise(function(resolve, reject){
+      setTimeout(function(){
+        resolve('promise 2');
+      }, 100);
+    });
+
+    Promise.race([promise1, promise2]).then(function(result){
+      // result === 'promise 2' because it was resolved before promise1
+      // was resolved.
+    });
+    ```
+
+    `Promise.race` is deterministic in that only the state of the first
+    settled promise matters. For example, even if other promises given to the
+    `promises` array argument are resolved, but the first settled promise has
+    become rejected before the other promises became fulfilled, the returned
+    promise will become rejected:
+
+    ```javascript
+    let promise1 = new Promise(function(resolve, reject){
+      setTimeout(function(){
+        resolve('promise 1');
+      }, 200);
+    });
+
+    let promise2 = new Promise(function(resolve, reject){
+      setTimeout(function(){
+        reject(new Error('promise 2'));
+      }, 100);
+    });
+
+    Promise.race([promise1, promise2]).then(function(result){
+      // Code here never runs
+    }, function(reason){
+      // reason.message === 'promise 2' because promise 2 became rejected before
+      // promise 1 became fulfilled
+    });
+    ```
+
+    An example real-world use case is implementing timeouts:
+
+    ```javascript
+    Promise.race([ajax('foo.json'), timeout(5000)])
+    ```
+
+    @method race
+    @static
+    @param {Array} promises array of promises to observe
+    Useful for tooling.
+    @return {Promise} a promise which settles in the same way as the first passed
+    promise to settle.
+  */
+  function race(entries) {
+    /*jshint validthis:true */
+    var Constructor = this;
+
+    if (!isArray(entries)) {
+      return new Constructor(function (_, reject) {
+        return reject(new TypeError('You must pass an array to race.'));
+      });
+    } else {
+      return new Constructor(function (resolve, reject) {
+        var length = entries.length;
+        for (var i = 0; i < length; i++) {
+          Constructor.resolve(entries[i]).then(resolve, reject);
+        }
+      });
+    }
+  }
+
+  /**
+    `Promise.reject` returns a promise rejected with the passed `reason`.
+    It is shorthand for the following:
+
+    ```javascript
+    let promise = new Promise(function(resolve, reject){
+      reject(new Error('WHOOPS'));
+    });
+
+    promise.then(function(value){
+      // Code here doesn't run because the promise is rejected!
+    }, function(reason){
+      // reason.message === 'WHOOPS'
+    });
+    ```
+
+    Instead of writing the above, your code now simply becomes the following:
+
+    ```javascript
+    let promise = Promise.reject(new Error('WHOOPS'));
+
+    promise.then(function(value){
+      // Code here doesn't run because the promise is rejected!
+    }, function(reason){
+      // reason.message === 'WHOOPS'
+    });
+    ```
+
+    @method reject
+    @static
+    @param {Any} reason value that the returned promise will be rejected with.
+    Useful for tooling.
+    @return {Promise} a promise rejected with the given `reason`.
+  */
+  function reject$1(reason) {
+    /*jshint validthis:true */
+    var Constructor = this;
+    var promise = new Constructor(noop);
+    reject(promise, reason);
+    return promise;
+  }
+
+  function needsResolver() {
+    throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
+  }
+
+  function needsNew() {
+    throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
+  }
+
+  /**
+    Promise objects represent the eventual result of an asynchronous operation. The
+    primary way of interacting with a promise is through its `then` method, which
+    registers callbacks to receive either a promise's eventual value or the reason
+    why the promise cannot be fulfilled.
+
+    Terminology
+    -----------
+
+    - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
+    - `thenable` is an object or function that defines a `then` method.
+    - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
+    - `exception` is a value that is thrown using the throw statement.
+    - `reason` is a value that indicates why a promise was rejected.
+    - `settled` the final resting state of a promise, fulfilled or rejected.
+
+    A promise can be in one of three states: pending, fulfilled, or rejected.
+
+    Promises that are fulfilled have a fulfillment value and are in the fulfilled
+    state.  Promises that are rejected have a rejection reason and are in the
+    rejected state.  A fulfillment value is never a thenable.
+
+    Promises can also be said to *resolve* a value.  If this value is also a
+    promise, then the original promise's settled state will match the value's
+    settled state.  So a promise that *resolves* a promise that rejects will
+    itself reject, and a promise that *resolves* a promise that fulfills will
+    itself fulfill.
+
+
+    Basic Usage:
+    ------------
+
+    ```js
+    let promise = new Promise(function(resolve, reject) {
+      // on success
+      resolve(value);
+
+      // on failure
+      reject(reason);
+    });
+
+    promise.then(function(value) {
+      // on fulfillment
+    }, function(reason) {
+      // on rejection
+    });
+    ```
+
+    Advanced Usage:
+    ---------------
+
+    Promises shine when abstracting away asynchronous interactions such as
+    `XMLHttpRequest`s.
+
+    ```js
+    function getJSON(url) {
+      return new Promise(function(resolve, reject){
+        let xhr = new XMLHttpRequest();
+
+        xhr.open('GET', url);
+        xhr.onreadystatechange = handler;
+        xhr.responseType = 'json';
+        xhr.setRequestHeader('Accept', 'application/json');
+        xhr.send();
+
+        function handler() {
+          if (this.readyState === this.DONE) {
+            if (this.status === 200) {
+              resolve(this.response);
+            } else {
+              reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
+            }
+          }
+        };
+      });
+    }
+
+    getJSON('/posts.json').then(function(json) {
+      // on fulfillment
+    }, function(reason) {
+      // on rejection
+    });
+    ```
+
+    Unlike callbacks, promises are great composable primitives.
+
+    ```js
+    Promise.all([
+      getJSON('/posts'),
+      getJSON('/comments')
+    ]).then(function(values){
+      values[0] // => postsJSON
+      values[1] // => commentsJSON
+
+      return values;
+    });
+    ```
+
+    @class Promise
+    @param {Function} resolver
+    Useful for tooling.
+    @constructor
+  */
+
+  var Promise$2 = function () {
+    function Promise(resolver) {
+      classCallCheck(this, Promise);
+
+      this[PROMISE_ID] = nextId();
+      this._result = this._state = undefined;
+      this._subscribers = [];
+
+      if (noop !== resolver) {
+        typeof resolver !== 'function' && needsResolver();
+        this instanceof Promise ? initializePromise(this, resolver) : needsNew();
+      }
+    }
+
+    /**
+    The primary way of interacting with a promise is through its `then` method,
+    which registers callbacks to receive either a promise's eventual value or the
+    reason why the promise cannot be fulfilled.
+     ```js
+    findUser().then(function(user){
+      // user is available
+    }, function(reason){
+      // user is unavailable, and you are given the reason why
+    });
+    ```
+     Chaining
+    --------
+     The return value of `then` is itself a promise.  This second, 'downstream'
+    promise is resolved with the return value of the first promise's fulfillment
+    or rejection handler, or rejected if the handler throws an exception.
+     ```js
+    findUser().then(function (user) {
+      return user.name;
+    }, function (reason) {
+      return 'default name';
+    }).then(function (userName) {
+      // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
+      // will be `'default name'`
+    });
+     findUser().then(function (user) {
+      throw new Error('Found user, but still unhappy');
+    }, function (reason) {
+      throw new Error('`findUser` rejected and we're unhappy');
+    }).then(function (value) {
+      // never reached
+    }, function (reason) {
+      // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
+      // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
+    });
+    ```
+    If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
+     ```js
+    findUser().then(function (user) {
+      throw new PedagogicalException('Upstream error');
+    }).then(function (value) {
+      // never reached
+    }).then(function (value) {
+      // never reached
+    }, function (reason) {
+      // The `PedgagocialException` is propagated all the way down to here
+    });
+    ```
+     Assimilation
+    ------------
+     Sometimes the value you want to propagate to a downstream promise can only be
+    retrieved asynchronously. This can be achieved by returning a promise in the
+    fulfillment or rejection handler. The downstream promise will then be pending
+    until the returned promise is settled. This is called *assimilation*.
+     ```js
+    findUser().then(function (user) {
+      return findCommentsByAuthor(user);
+    }).then(function (comments) {
+      // The user's comments are now available
+    });
+    ```
+     If the assimliated promise rejects, then the downstream promise will also reject.
+     ```js
+    findUser().then(function (user) {
+      return findCommentsByAuthor(user);
+    }).then(function (comments) {
+      // If `findCommentsByAuthor` fulfills, we'll have the value here
+    }, function (reason) {
+      // If `findCommentsByAuthor` rejects, we'll have the reason here
+    });
+    ```
+     Simple Example
+    --------------
+     Synchronous Example
+     ```javascript
+    let result;
+     try {
+      result = findResult();
+      // success
+    } catch(reason) {
+      // failure
+    }
+    ```
+     Errback Example
+     ```js
+    findResult(function(result, err){
+      if (err) {
+        // failure
+      } else {
+        // success
+      }
+    });
+    ```
+     Promise Example;
+     ```javascript
+    findResult().then(function(result){
+      // success
+    }, function(reason){
+      // failure
+    });
+    ```
+     Advanced Example
+    --------------
+     Synchronous Example
+     ```javascript
+    let author, books;
+     try {
+      author = findAuthor();
+      books  = findBooksByAuthor(author);
+      // success
+    } catch(reason) {
+      // failure
+    }
+    ```
+     Errback Example
+     ```js
+     function foundBooks(books) {
+     }
+     function failure(reason) {
+     }
+     findAuthor(function(author, err){
+      if (err) {
+        failure(err);
+        // failure
+      } else {
+        try {
+          findBoooksByAuthor(author, function(books, err) {
+            if (err) {
+              failure(err);
+            } else {
+              try {
+                foundBooks(books);
+              } catch(reason) {
+                failure(reason);
+              }
+            }
+          });
+        } catch(error) {
+          failure(err);
+        }
+        // success
+      }
+    });
+    ```
+     Promise Example;
+     ```javascript
+    findAuthor().
+      then(findBooksByAuthor).
+      then(function(books){
+        // found books
+    }).catch(function(reason){
+      // something went wrong
+    });
+    ```
+     @method then
+    @param {Function} onFulfilled
+    @param {Function} onRejected
+    Useful for tooling.
+    @return {Promise}
+    */
+
+    /**
+    `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
+    as the catch block of a try/catch statement.
+    ```js
+    function findAuthor(){
+    throw new Error('couldn't find that author');
+    }
+    // synchronous
+    try {
+    findAuthor();
+    } catch(reason) {
+    // something went wrong
+    }
+    // async with promises
+    findAuthor().catch(function(reason){
+    // something went wrong
+    });
+    ```
+    @method catch
+    @param {Function} onRejection
+    Useful for tooling.
+    @return {Promise}
+    */
+
+
+    createClass(Promise, [{
+      key: 'catch',
+      value: function _catch(onRejection) {
+        return this.then(null, onRejection);
+      }
+
+      /**
+        `finally` will be invoked regardless of the promise's fate just as native
+        try/catch/finally behaves
+      
+        Synchronous example:
+      
+        ```js
+        findAuthor() {
+          if (Math.random() > 0.5) {
+            throw new Error();
+          }
+          return new Author();
+        }
+      
+        try {
+          return findAuthor(); // succeed or fail
+        } catch(error) {
+          return findOtherAuther();
+        } finally {
+          // always runs
+          // doesn't affect the return value
+        }
+        ```
+      
+        Asynchronous example:
+      
+        ```js
+        findAuthor().catch(function(reason){
+          return findOtherAuther();
+        }).finally(function(){
+          // author was either found, or not
+        });
+        ```
+      
+        @method finally
+        @param {Function} callback
+        @return {Promise}
+      */
+
+    }, {
+      key: 'finally',
+      value: function _finally(callback) {
+        var promise = this;
+        var constructor = promise.constructor;
+
+        if (isFunction(callback)) {
+          return promise.then(function (value) {
+            return constructor.resolve(callback()).then(function () {
+              return value;
+            });
+          }, function (reason) {
+            return constructor.resolve(callback()).then(function () {
+              throw reason;
+            });
+          });
+        }
+
+        return promise.then(callback, callback);
+      }
+    }]);
+    return Promise;
+  }();
+
+  Promise$2.prototype.then = then;
+  Promise$2.all = all;
+  Promise$2.race = race;
+  Promise$2.resolve = resolve$1;
+  Promise$2.reject = reject$1;
+  Promise$2._setScheduler = setScheduler;
+  Promise$2._setAsap = setAsap;
+  Promise$2._asap = asap;
+
+  /*global self*/
+  function polyfill() {
+    var local = void 0;
+
+    if (typeof global !== 'undefined') {
+      local = global;
+    } else if (typeof self !== 'undefined') {
+      local = self;
+    } else {
+      try {
+        local = Function('return this')();
+      } catch (e) {
+        throw new Error('polyfill failed because global object is unavailable in this environment');
+      }
+    }
+
+    var P = local.Promise;
+
+    if (P) {
+      var promiseToString = null;
+      try {
+        promiseToString = Object.prototype.toString.call(P.resolve());
+      } catch (e) {
+        // silently ignored
+      }
+
+      if (promiseToString === '[object Promise]' && !P.cast) {
+        return;
+      }
+    }
+
+    local.Promise = Promise$2;
+  }
+
+  // Strange compat..
+  Promise$2.polyfill = polyfill;
+  Promise$2.Promise = Promise$2;
+
+  var Promise$1 = typeof Promise !== "undefined" ? Promise : Promise$2;
+
   // Register logging callbacks
   function registerLoggingCallbacks(obj) {
        var i,
   }
 
   function runLoggingCallbacks(key, args) {
-       var i, l, callbacks;
-
-       callbacks = config.callbacks[key];
-       for (i = 0, l = callbacks.length; i < l; i++) {
-               callbacks[i](args);
+       var callbacks = config.callbacks[key];
+
+       // Handling 'log' callbacks separately. Unlike the other callbacks,
+       // the log callback is not controlled by the processing queue,
+       // but rather used by asserts. Hence to promisfy the 'log' callback
+       // would mean promisfying each step of a test
+       if (key === "log") {
+               callbacks.map(function (callback) {
+                       return callback(args);
+               });
+               return;
        }
+
+       // ensure that each callback is executed serially
+       return callbacks.reduce(function (promiseChain, callback) {
+               return promiseChain.then(function () {
+                       return Promise$1.resolve(callback(args));
+               });
+       }, Promise$1.resolve([]));
   }
 
   // Doesn't support IE9, it will return undefined on these browsers
   function advance() {
        advanceTaskQueue();
 
-       if (!taskQueue.length) {
+       if (!taskQueue.length && !config.blocking && !config.current) {
                advanceTestQueue();
        }
   }
 
   /**
-   * Advances the taskQueue to the next task if it is ready and not empty.
+   * Advances the taskQueue with an increased depth
    */
   function advanceTaskQueue() {
        var start = now();
        config.depth = (config.depth || 0) + 1;
 
-       while (taskQueue.length && !config.blocking) {
+       processTaskQueue(start);
+
+       config.depth--;
+  }
+
+  /**
+   * Process the first task on the taskQueue as a promise.
+   * Each task is a function returned by https://github.com/qunitjs/qunit/blob/master/src/test.js#L381
+   */
+  function processTaskQueue(start) {
+       if (taskQueue.length && !config.blocking) {
                var elapsedTime = now() - start;
 
                if (!defined.setTimeout || config.updateRate <= 0 || elapsedTime < config.updateRate) {
                        var task = taskQueue.shift();
-                       task();
+                       Promise$1.resolve(task()).then(function () {
+                               if (!taskQueue.length) {
+                                       advance();
+                               } else {
+                                       processTaskQueue(start);
+                               }
+                       });
                } else {
-                       setTimeout(advance);
-                       break;
+                       setTimeout$1(advance);
                }
        }
-
-       config.depth--;
   }
 
   /**
                failed: config.stats.bad,
                total: config.stats.all,
                runtime: runtime
-       });
+       }).then(function () {
 
-       // Clear own storage items if all tests passed
-       if (storage && config.stats.bad === 0) {
-               for (var i = storage.length - 1; i >= 0; i--) {
-                       var key = storage.key(i);
+               // Clear own storage items if all tests passed
+               if (storage && config.stats.bad === 0) {
+                       for (var i = storage.length - 1; i >= 0; i--) {
+                               var key = storage.key(i);
 
-                       if (key.indexOf("qunit-test-") === 0) {
-                               storage.removeItem(key);
+                               if (key.indexOf("qunit-test-") === 0) {
+                                       storage.removeItem(key);
+                               }
                        }
                }
-       }
+       });
   }
 
   var ProcessingQueue = {
                key: "start",
                value: function start(recordTime) {
                        if (recordTime) {
-                               this._startTime = Date.now();
+                               this._startTime = performanceNow();
+                               if (performance) {
+                                       performance.mark("qunit_test_start");
+                               }
                        }
 
                        return {
                key: "end",
                value: function end(recordTime) {
                        if (recordTime) {
-                               this._endTime = Date.now();
+                               this._endTime = performanceNow();
+                               if (performance) {
+                                       performance.mark("qunit_test_end");
+
+                                       var testName = this.fullName.join(" – ");
+
+                                       measure("QUnit Test: " + testName, "qunit_test_start", "qunit_test_end");
+                               }
                        }
 
                        return extend(this.start(), {
                module = module.parentModule;
        }
 
-       return modules;
+       // The above push modules from the child to the parent
+       // return a reversed order with the top being the top most parent module
+       return modules.reverse();
   }
 
   Test.prototype = {
        before: function before() {
-               var i,
-                   startModule,
-                   module = this.module,
+               var _this = this;
+
+               var module = this.module,
                    notStartedModules = getNotStartedModules(module);
 
-               for (i = notStartedModules.length - 1; i >= 0; i--) {
-                       startModule = notStartedModules[i];
-                       startModule.stats = { all: 0, bad: 0, started: now() };
-                       emit("suiteStart", startModule.suiteReport.start(true));
-                       runLoggingCallbacks("moduleStart", {
-                               name: startModule.name,
-                               tests: startModule.tests
+               // ensure the callbacks are executed serially for each module
+               var callbackPromises = notStartedModules.reduce(function (promiseChain, startModule) {
+                       return promiseChain.then(function () {
+                               startModule.stats = { all: 0, bad: 0, started: now() };
+                               emit("suiteStart", startModule.suiteReport.start(true));
+                               return runLoggingCallbacks("moduleStart", {
+                                       name: startModule.name,
+                                       tests: startModule.tests
+                               });
                        });
-               }
+               }, Promise$1.resolve([]));
 
-               config.current = this;
-
-               this.testEnvironment = extend({}, module.testEnvironment);
+               return callbackPromises.then(function () {
+                       config.current = _this;
 
-               this.started = now();
-               emit("testStart", this.testReport.start(true));
-               runLoggingCallbacks("testStart", {
-                       name: this.testName,
-                       module: module.name,
-                       testId: this.testId,
-                       previousFailure: this.previousFailure
+                       _this.testEnvironment = extend({}, module.testEnvironment);
+
+                       _this.started = now();
+                       emit("testStart", _this.testReport.start(true));
+                       return runLoggingCallbacks("testStart", {
+                               name: _this.testName,
+                               module: module.name,
+                               testId: _this.testId,
+                               previousFailure: _this.previousFailure
+                       }).then(function () {
+                               if (!config.pollution) {
+                                       saveGlobal();
+                               }
+                       });
                });
-
-               if (!config.pollution) {
-                       saveGlobal();
-               }
        },
 
        run: function run() {
        },
 
        queueHook: function queueHook(hook, hookName, hookOwner) {
-               var _this = this;
+               var _this2 = this;
 
                var callHook = function callHook() {
-                       var promise = hook.call(_this.testEnvironment, _this.assert);
-                       _this.resolvePromise(promise, hookName);
+                       var promise = hook.call(_this2.testEnvironment, _this2.assert);
+                       _this2.resolvePromise(promise, hookName);
                };
 
                var runHook = function runHook() {
                                        return;
                                }
 
-                               _this.preserveEnvironment = true;
+                               _this2.preserveEnvironment = true;
                        }
 
                        // The 'after' hook should only execute when there are not tests left and
                                return;
                        }
 
-                       config.current = _this;
+                       config.current = _this2;
                        if (config.notrycatch) {
                                callHook();
                                return;
                        try {
                                callHook();
                        } catch (error) {
-                               _this.pushFailure(hookName + " failed on " + _this.testName + ": " + (error.message || error), extractStacktrace(error, 0));
+                               _this2.pushFailure(hookName + " failed on " + _this2.testName + ": " + (error.message || error), extractStacktrace(error, 0));
                        }
                };
 
                emit("testEnd", this.testReport.end(true));
                this.testReport.slimAssertions();
 
-               runLoggingCallbacks("testDone", {
+               return runLoggingCallbacks("testDone", {
                        name: testName,
                        module: moduleName,
                        skipped: skipped,
 
                        // Source of Test
                        source: this.stack
-               });
-
-               if (module.testsRun === numberOfTests(module)) {
-                       logSuiteEnd(module);
+               }).then(function () {
+                       if (module.testsRun === numberOfTests(module)) {
+                               var completedModules = [module];
+
+                               // Check if the parent modules, iteratively, are done. If that the case,
+                               // we emit the `suiteEnd` event and trigger `moduleDone` callback.
+                               var parent = module.parentModule;
+                               while (parent && parent.testsRun === numberOfTests(parent)) {
+                                       completedModules.push(parent);
+                                       parent = parent.parentModule;
+                               }
 
-                       // Check if the parent modules, iteratively, are done. If that the case,
-                       // we emit the `suiteEnd` event and trigger `moduleDone` callback.
-                       var parent = module.parentModule;
-                       while (parent && parent.testsRun === numberOfTests(parent)) {
-                               logSuiteEnd(parent);
-                               parent = parent.parentModule;
+                               return completedModules.reduce(function (promiseChain, completedModule) {
+                                       return promiseChain.then(function () {
+                                               return logSuiteEnd(completedModule);
+                                       });
+                               }, Promise$1.resolve([]));
                        }
-               }
-
-               config.current = undefined;
+               }).then(function () {
+                       config.current = undefined;
+               });
 
                function logSuiteEnd(module) {
 
                        module.hooks = {};
 
                        emit("suiteEnd", module.suiteReport.end(true));
-                       runLoggingCallbacks("moduleDone", {
+                       return runLoggingCallbacks("moduleDone", {
                                name: module.name,
                                tests: module.tests,
                                failed: module.stats.bad,
 
                function runTest() {
                        return [function () {
-                               test.before();
+                               return test.before();
                        }].concat(toConsumableArray(test.hooks("before")), [function () {
                                test.preserveTestEnvironment();
                        }], toConsumableArray(test.hooks("beforeEach")), [function () {
                        }], toConsumableArray(test.hooks("afterEach").reverse()), toConsumableArray(test.hooks("after").reverse()), [function () {
                                test.after();
                        }, function () {
-                               test.finish();
+                               return test.finish();
                        }]);
                }
 
 
                if (typeof timeoutDuration === "number" && timeoutDuration > 0) {
                        clearTimeout(config.timeout);
-                       config.timeout = setTimeout(function () {
+                       config.timeout = setTimeout$1(function () {
                                pushFailure("Test took longer than " + timeoutDuration + "ms; test timed out.", sourceFromStacktrace(2));
                                internalRecover(test);
                        }, timeoutDuration);
                if (config.timeout) {
                        clearTimeout(config.timeout);
                }
-               config.timeout = setTimeout(function () {
+               config.timeout = setTimeout$1(function () {
                        if (test.semaphore > 0) {
                                return;
                        }
        }
   }
 
-  /**
-   * Returns a function that proxies to the given method name on the globals
-   * console object. The proxy will also detect if the console doesn't exist and
-   * will appropriately no-op. This allows support for IE9, which doesn't have a
-   * console if the developer tools are not open.
-   */
-  function consoleProxy(method) {
-       return function () {
-               if (console) {
-                       console[method].apply(console, arguments);
-               }
-       };
-  }
-
-  var Logger = {
-       warn: consoleProxy("warn")
-  };
-
   var Assert = function () {
        function Assert(testContext) {
                classCallCheck(this, Assert);
                                // We don't want to validate thrown error
                                if (!expected) {
                                        result = true;
-                                       expected = null;
 
                                        // Expected is a regexp
                                } else if (expectedType === "regexp") {
                                        result = expected.test(errorString(actual));
 
+                                       // Log the string form of the regexp
+                                       expected = String(expected);
+
                                        // Expected is a constructor, maybe an Error constructor
                                } else if (expectedType === "function" && actual instanceof expected) {
                                        result = true;
                                } else if (expectedType === "object") {
                                        result = actual instanceof expected.constructor && actual.name === expected.name && actual.message === expected.message;
 
+                                       // Log the string form of the Error object
+                                       expected = errorString(expected);
+
                                        // Expected is a validation function which returns true if validation passed
                                } else if (expectedType === "function" && expected.call({}, actual) === true) {
                                        expected = null;
 
                        currentTest.assert.pushResult({
                                result: result,
-                               actual: actual,
+
+                               // undefined if it didn't throw
+                               actual: actual && errorString(actual),
                                expected: expected,
                                message: message
                        });
                                // We don't want to validate
                                if (expected === undefined) {
                                        result = true;
-                                       expected = actual;
 
                                        // Expected is a regexp
                                } else if (expectedType === "regexp") {
                                        result = expected.test(errorString(actual));
 
+                                       // Log the string form of the regexp
+                                       expected = String(expected);
+
                                        // Expected is a constructor, maybe an Error constructor
                                } else if (expectedType === "function" && actual instanceof expected) {
                                        result = true;
                                } else if (expectedType === "object") {
                                        result = actual instanceof expected.constructor && actual.name === expected.name && actual.message === expected.message;
 
+                                       // Log the string form of the Error object
+                                       expected = errorString(expected);
+
                                        // Expected is a validation function which returns true if validation passed
                                } else {
                                        if (expectedType === "function") {
 
                                currentTest.assert.pushResult({
                                        result: result,
-                                       actual: actual,
+
+                                       // leave rejection value of undefined as-is
+                                       actual: actual && errorString(actual),
                                        expected: expected,
                                        message: message
                                });
   /**
    * Converts an error into a simple string for comparisons.
    *
-   * @param {Error} error
+   * @param {Error|Object} error
    * @return {String}
    */
   function errorString(error) {
        var resultErrorString = error.toString();
 
+       // If the error wasn't a subclass of Error but something like
+       // an object literal with name and message properties...
        if (resultErrorString.substring(0, 7) === "[object") {
                var name = error.name ? error.name.toString() : "Error";
                var message = error.message ? error.message.toString() : "";
        if (defined.document) {
 
                // QUnit may be defined when it is preconfigured but then only QUnit and QUnit.config may be defined.
-               if (window.QUnit && window.QUnit.version) {
+               if (window$1.QUnit && window$1.QUnit.version) {
                        throw new Error("QUnit has already been defined.");
                }
 
-               window.QUnit = QUnit;
+               window$1.QUnit = QUnit;
        }
 
        // For nodejs
                if (config.current.ignoreGlobalErrors) {
                        return true;
                }
-               pushFailure.apply(undefined, [error.message, error.fileName + ":" + error.lineNumber].concat(args));
+               pushFailure.apply(undefined, [error.message, error.stacktrace || error.fileName + ":" + error.lineNumber].concat(args));
        } else {
                test("global failure", extend(function () {
-                       pushFailure.apply(undefined, [error.message, error.fileName + ":" + error.lineNumber].concat(args));
+                       pushFailure.apply(undefined, [error.message, error.stacktrace || error.fileName + ":" + error.lineNumber].concat(args));
                }, { validTest: true }));
        }
 
   var runStarted = false;
 
   // Figure out if we're running the tests from a server or not
-  QUnit.isLocal = !(defined.document && window.location.protocol !== "file:");
+  QUnit.isLocal = !(defined.document && window$1.location.protocol !== "file:");
 
   // Expose the current QUnit version
-  QUnit.version = "2.6.2";
+  QUnit.version = "2.9.1";
 
   extend(QUnit, {
        on: on,
 
        // Add a slight delay to allow definition of more modules and tests.
        if (defined.setTimeout) {
-               setTimeout(function () {
+               setTimeout$1(function () {
                        begin();
                });
        } else {
        }
   }
 
+  function unblockAndAdvanceQueue() {
+       config.blocking = false;
+       ProcessingQueue.advance();
+  }
+
   function begin() {
        var i,
            l,
                runLoggingCallbacks("begin", {
                        totalTests: Test.count,
                        modules: modulesLog
-               });
+               }).then(unblockAndAdvanceQueue);
+       } else {
+               unblockAndAdvanceQueue();
        }
-
-       config.blocking = false;
-       ProcessingQueue.advance();
   }
 
   exportQUnit(QUnit);
 
   (function () {
 
-       if (typeof window === "undefined" || typeof document === "undefined") {
+       if (typeof window$1 === "undefined" || typeof document$1 === "undefined") {
                return;
        }
 
                        return;
                }
 
-               var fixture = document.getElementById("qunit-fixture");
+               var fixture = document$1.getElementById("qunit-fixture");
                if (fixture) {
                        config.fixture = fixture.cloneNode(true);
                }
                        return;
                }
 
-               var fixture = document.getElementById("qunit-fixture");
+               var fixture = document$1.getElementById("qunit-fixture");
                var resetFixtureType = _typeof(config.fixture);
                if (resetFixtureType === "string") {
 
                        // support user defined values for `config.fixture`
-                       var newFixture = document.createElement("div");
+                       var newFixture = document$1.createElement("div");
                        newFixture.setAttribute("id", "qunit-fixture");
                        newFixture.innerHTML = config.fixture;
                        fixture.parentNode.replaceChild(newFixture, fixture);
   (function () {
 
        // Only interact with URLs via window.location
-       var location = typeof window !== "undefined" && window.location;
+       var location = typeof window$1 !== "undefined" && window$1.location;
        if (!location) {
                return;
        }
   (function () {
 
        // Don't load the HTML Reporter on non-browser environments
-       if (typeof window === "undefined" || !window.document) {
+       if (typeof window$1 === "undefined" || !window$1.document) {
                return;
        }
 
        var config = QUnit.config,
-           document$$1 = window.document,
+           hiddenTests = [],
+           document = window$1.document,
            collapseNext = false,
            hasOwn = Object.prototype.hasOwnProperty,
            unfilteredUrl = setUrl({ filter: undefined, module: undefined,
        }
 
        function id(name) {
-               return document$$1.getElementById && document$$1.getElementById(name);
+               return document.getElementById && document.getElementById(name);
        }
 
        function abortTests() {
                updatedUrl = setUrl(params);
 
                // Check if we can apply the change without a page refresh
-               if ("hidepassed" === field.name && "replaceState" in window.history) {
+               if ("hidepassed" === field.name && "replaceState" in window$1.history) {
                        QUnit.urlParams[field.name] = value;
                        config[field.name] = value || false;
                        tests = id("qunit-tests");
                        if (tests) {
-                               toggleClass(tests, "hidepass", value || false);
+                               var length = tests.children.length;
+                               var children = tests.children;
+
+                               if (field.checked) {
+                                       for (var i = 0; i < length; i++) {
+                                               var test = children[i];
+
+                                               if (test && test.className.indexOf("pass") > -1) {
+                                                       hiddenTests.push(test);
+                                               }
+                                       }
+
+                                       var _iteratorNormalCompletion = true;
+                                       var _didIteratorError = false;
+                                       var _iteratorError = undefined;
+
+                                       try {
+                                               for (var _iterator = hiddenTests[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
+                                                       var hiddenTest = _step.value;
+
+                                                       tests.removeChild(hiddenTest);
+                                               }
+                                       } catch (err) {
+                                               _didIteratorError = true;
+                                               _iteratorError = err;
+                                       } finally {
+                                               try {
+                                                       if (!_iteratorNormalCompletion && _iterator.return) {
+                                                               _iterator.return();
+                                                       }
+                                               } finally {
+                                                       if (_didIteratorError) {
+                                                               throw _iteratorError;
+                                                       }
+                                               }
+                                       }
+                               } else {
+                                       while ((test = hiddenTests.pop()) != null) {
+                                               tests.appendChild(test);
+                                       }
+                               }
                        }
-                       window.history.replaceState(null, "", updatedUrl);
+                       window$1.history.replaceState(null, "", updatedUrl);
                } else {
-                       window.location = updatedUrl;
+                       window$1.location = updatedUrl;
                }
        }
 
                    arrValue,
                    i,
                    querystring = "?",
-                   location = window.location;
+                   location = window$1.location;
 
                params = QUnit.extend(QUnit.extend({}, QUnit.urlParams), params);
 
                        }
                }
 
-               window.location = setUrl({
+               window$1.location = setUrl({
                        filter: filter === "" ? undefined : filter,
                        moduleId: selectedModules.length === 0 ? undefined : selectedModules,
 
        }
 
        function toolbarUrlConfigContainer() {
-               var urlConfigContainer = document$$1.createElement("span");
+               var urlConfigContainer = document.createElement("span");
 
                urlConfigContainer.innerHTML = getUrlConfigHtml();
                addClass(urlConfigContainer, "qunit-url-config");
        }
 
        function abortTestsButton() {
-               var button = document$$1.createElement("button");
+               var button = document.createElement("button");
                button.id = "qunit-abort-tests-button";
                button.innerHTML = "Abort";
                addEvent(button, "click", abortTests);
        }
 
        function toolbarLooseFilter() {
-               var filter = document$$1.createElement("form"),
-                   label = document$$1.createElement("label"),
-                   input = document$$1.createElement("input"),
-                   button = document$$1.createElement("button");
+               var filter = document.createElement("form"),
+                   label = document.createElement("label"),
+                   input = document.createElement("input"),
+                   button = document.createElement("button");
 
                addClass(filter, "qunit-filter");
 
                label.appendChild(input);
 
                filter.appendChild(label);
-               filter.appendChild(document$$1.createTextNode(" "));
+               filter.appendChild(document.createTextNode(" "));
                filter.appendChild(button);
                addEvent(filter, "submit", interceptNavigation);
 
                var allCheckbox,
                    commit,
                    reset,
-                   moduleFilter = document$$1.createElement("form"),
-                   label = document$$1.createElement("label"),
-                   moduleSearch = document$$1.createElement("input"),
-                   dropDown = document$$1.createElement("div"),
-                   actions = document$$1.createElement("span"),
-                   dropDownList = document$$1.createElement("ul"),
+                   moduleFilter = document.createElement("form"),
+                   label = document.createElement("label"),
+                   moduleSearch = document.createElement("input"),
+                   dropDown = document.createElement("div"),
+                   actions = document.createElement("span"),
+                   dropDownList = document.createElement("ul"),
                    dirty = false;
 
                moduleSearch.id = "qunit-modulefilter-search";
                label.appendChild(moduleSearch);
 
                actions.id = "qunit-modulefilter-actions";
-               actions.innerHTML = "<button style='display:none'>Apply</button>" + "<button type='reset' style='display:none'>Reset</button>" + "<label class='clickable" + (config.moduleId.length ? "" : " checked") + "'><input type='checkbox'" + (config.moduleId.length ? "" : " checked='checked'") + ">All modules</label>";
+               actions.innerHTML = "<button style='display:none'>Apply</button>" + "<button type='reset' style='display:none'>Reset</button>" + "<label class='clickable" + (config.moduleId.length ? "" : " checked") + "'><input type='checkbox'" + (config.moduleId.length ? "" : " checked='checked'") + " />All modules</label>";
                allCheckbox = actions.lastChild.firstChild;
                commit = actions.firstChild;
                reset = commit.nextSibling;
                addEvent(moduleFilter, "reset", function () {
 
                        // Let the reset happen, then update styles
-                       window.setTimeout(selectionChange);
+                       window$1.setTimeout(selectionChange);
                });
 
                // Enables show/hide for the dropdown
                        }
 
                        dropDown.style.display = "block";
-                       addEvent(document$$1, "click", hideHandler);
-                       addEvent(document$$1, "keydown", hideHandler);
+                       addEvent(document, "click", hideHandler);
+                       addEvent(document, "keydown", hideHandler);
 
                        // Hide on Escape keydown or outside-container click
                        function hideHandler(e) {
                                                moduleSearch.focus();
                                        }
                                        dropDown.style.display = "none";
-                                       removeEvent(document$$1, "click", hideHandler);
-                                       removeEvent(document$$1, "keydown", hideHandler);
+                                       removeEvent(document, "click", hideHandler);
+                                       removeEvent(document, "keydown", hideHandler);
                                        moduleSearch.value = "";
                                        searchInput();
                                }
                        toolbar.appendChild(toolbarUrlConfigContainer());
                        toolbar.appendChild(toolbarModuleFilter());
                        toolbar.appendChild(toolbarLooseFilter());
-                       toolbar.appendChild(document$$1.createElement("div")).className = "clearfix";
+                       toolbar.appendChild(document.createElement("div")).className = "clearfix";
                }
        }
 
 
                if (tests) {
                        tests.innerHTML = "";
-                       result = document$$1.createElement("p");
+                       result = document.createElement("p");
                        result.id = "qunit-testresult";
                        result.className = "result";
                        tests.parentNode.insertBefore(result, tests);
 
                if (userAgent) {
                        userAgent.innerHTML = "";
-                       userAgent.appendChild(document$$1.createTextNode("QUnit " + QUnit.version + "; " + navigator.userAgent));
+                       userAgent.appendChild(document.createTextNode("QUnit " + QUnit.version + "; " + navigator.userAgent));
                }
        }
 
                var qunit = id("qunit");
 
                if (qunit) {
-                       qunit.innerHTML = "<h1 id='qunit-header'>" + escapeText(document$$1.title) + "</h1>" + "<h2 id='qunit-banner'></h2>" + "<div id='qunit-testrunner-toolbar'></div>" + appendFilteredTest() + "<h2 id='qunit-userAgent'></h2>" + "<ol id='qunit-tests'></ol>";
+                       qunit.innerHTML = "<h1 id='qunit-header'>" + escapeText(document.title) + "</h1>" + "<h2 id='qunit-banner'></h2>" + "<div id='qunit-testrunner-toolbar'></div>" + appendFilteredTest() + "<h2 id='qunit-userAgent'></h2>" + "<ol id='qunit-tests'></ol>";
                }
 
                appendHeader();
                appendToolbar();
        }
 
-       function appendTestsList(modules) {
-               var i, l, x, z, test, moduleObj;
-
-               for (i = 0, l = modules.length; i < l; i++) {
-                       moduleObj = modules[i];
-
-                       for (x = 0, z = moduleObj.tests.length; x < z; x++) {
-                               test = moduleObj.tests[x];
-
-                               appendTest(test.name, test.testId, moduleObj.name);
-                       }
-               }
-       }
-
        function appendTest(name, testId, moduleName) {
                var title,
                    rerunTrigger,
                        return;
                }
 
-               title = document$$1.createElement("strong");
+               title = document.createElement("strong");
                title.innerHTML = getNameHtml(name, moduleName);
 
-               rerunTrigger = document$$1.createElement("a");
+               rerunTrigger = document.createElement("a");
                rerunTrigger.innerHTML = "Rerun";
                rerunTrigger.href = setUrl({ testId: testId });
 
-               testBlock = document$$1.createElement("li");
+               testBlock = document.createElement("li");
                testBlock.appendChild(title);
                testBlock.appendChild(rerunTrigger);
                testBlock.id = "qunit-test-output-" + testId;
 
-               assertList = document$$1.createElement("ol");
+               assertList = document.createElement("ol");
                assertList.className = "qunit-assert-list";
 
                testBlock.appendChild(assertList);
 
        // HTML Reporter initialization and load
        QUnit.begin(function (details) {
-               var i, moduleObj, tests;
+               var i, moduleObj;
 
                // Sort modules by name for the picker
                for (i = 0; i < details.modules.length; i++) {
 
                // Initialize QUnit elements
                appendInterface();
-               appendTestsList(details.modules);
-               tests = id("qunit-tests");
-               if (tests && config.hidepassed) {
-                       addClass(tests, "hidepass");
-               }
        });
 
        QUnit.done(function (details) {
                                if (test.className === "" || test.className === "running") {
                                        test.className = "aborted";
                                        assertList = test.getElementsByTagName("ol")[0];
-                                       assertLi = document$$1.createElement("li");
+                                       assertLi = document.createElement("li");
                                        assertLi.className = "fail";
                                        assertLi.innerHTML = "Test aborted.";
                                        assertList.appendChild(assertLi);
                        id("qunit-testresult-display").innerHTML = html;
                }
 
-               if (config.altertitle && document$$1.title) {
+               if (config.altertitle && document.title) {
 
                        // Show ✖ for good, ✔ for bad suite result in title
                        // use escape sequences in case file gets loaded with non-utf-8
                        // charset
-                       document$$1.title = [stats.failedTests ? "\u2716" : "\u2714", document$$1.title.replace(/^[\u2714\u2716] /i, "")].join(" ");
+                       document.title = [stats.failedTests ? "\u2716" : "\u2714", document.title.replace(/^[\u2714\u2716] /i, "")].join(" ");
                }
 
                // Scroll back to top to show results
-               if (config.scrolltop && window.scrollTo) {
-                       window.scrollTo(0, 0);
+               if (config.scrolltop && window$1.scrollTo) {
+                       window$1.scrollTo(0, 0);
                }
        });
 
        }
 
        QUnit.testStart(function (details) {
-               var running, testBlock, bad;
+               var running, bad;
 
-               testBlock = id("qunit-test-output-" + details.testId);
-               if (testBlock) {
-                       testBlock.className = "running";
-               } else {
-
-                       // Report later registered tests
-                       appendTest(details.name, details.testId, details.module);
-               }
+               appendTest(details.name, details.testId, details.module);
 
                running = id("qunit-testresult-display");
+
                if (running) {
+                       addClass(running, "running");
+
                        bad = QUnit.config.reorder && details.previousFailure;
 
                        running.innerHTML = [bad ? "Rerunning previously failed test: <br />" : "Running: <br />", getNameHtml(details.name, details.module)].join("");
 
                assertList = testItem.getElementsByTagName("ol")[0];
 
-               assertLi = document$$1.createElement("li");
+               assertLi = document.createElement("li");
                assertLi.className = details.result ? "pass" : "fail";
                assertLi.innerHTML = message;
                assertList.appendChild(assertLi);
                    time,
                    testItem,
                    assertList,
+                   status,
                    good,
                    bad,
                    testCounts,
 
                testItem = id("qunit-test-output-" + details.testId);
 
+               removeClass(testItem, "running");
+
+               if (details.failed > 0) {
+                       status = "failed";
+               } else if (details.todo) {
+                       status = "todo";
+               } else {
+                       status = details.skipped ? "skipped" : "passed";
+               }
+
                assertList = testItem.getElementsByTagName("ol")[0];
 
                good = details.passed;
                        stats.skippedTests++;
 
                        testItem.className = "skipped";
-                       skipped = document$$1.createElement("em");
+                       skipped = document.createElement("em");
                        skipped.className = "qunit-skipped-label";
                        skipped.innerHTML = "skipped";
                        testItem.insertBefore(skipped, testTitle);
                        testItem.className = testPassed ? "pass" : "fail";
 
                        if (details.todo) {
-                               var todoLabel = document$$1.createElement("em");
+                               var todoLabel = document.createElement("em");
                                todoLabel.className = "qunit-todo-label";
                                todoLabel.innerHTML = "todo";
                                testItem.className += " todo";
                                testItem.insertBefore(todoLabel, testTitle);
                        }
 
-                       time = document$$1.createElement("span");
+                       time = document.createElement("span");
                        time.className = "runtime";
                        time.innerHTML = details.runtime + " ms";
                        testItem.insertBefore(time, assertList);
 
                // Show the source of the test when showing assertions
                if (details.source) {
-                       sourceName = document$$1.createElement("p");
-                       sourceName.innerHTML = "<strong>Source: </strong>" + details.source;
+                       sourceName = document.createElement("p");
+                       sourceName.innerHTML = "<strong>Source: </strong>" + escapeText(details.source);
                        addClass(sourceName, "qunit-source");
                        if (testPassed) {
                                addClass(sourceName, "qunit-collapsed");
                        });
                        testItem.appendChild(sourceName);
                }
+
+               if (config.hidepassed && status === "passed") {
+
+                       // use removeChild instead of remove because of support
+                       hiddenTests.push(testItem);
+
+                       tests.removeChild(testItem);
+               }
        });
 
        // Avoid readyState issue with phantomjs
        // Ref: #818
        var notPhantom = function (p) {
                return !(p && p.version && p.version.major > 0);
-       }(window.phantom);
+       }(window$1.phantom);
 
-       if (notPhantom && document$$1.readyState === "complete") {
+       if (notPhantom && document.readyState === "complete") {
                QUnit.load();
        } else {
-               addEvent(window, "load", QUnit.load);
+               addEvent(window$1, "load", QUnit.load);
        }
 
        // Wrap window.onerror. We will call the original window.onerror to see if
        // the existing handler fully handles the error; if not, we will call the
        // QUnit.onError function.
-       var originalWindowOnError = window.onerror;
+       var originalWindowOnError = window$1.onerror;
 
        // Cover uncaught exceptions
        // Returning true will suppress the default browser handler,
        // returning false will let it run.
-       window.onerror = function (message, fileName, lineNumber) {
+       window$1.onerror = function (message, fileName, lineNumber, columnNumber, errorObj) {
                var ret = false;
                if (originalWindowOnError) {
-                       for (var _len = arguments.length, args = Array(_len > 3 ? _len - 3 : 0), _key = 3; _key < _len; _key++) {
-                               args[_key - 3] = arguments[_key];
+                       for (var _len = arguments.length, args = Array(_len > 5 ? _len - 5 : 0), _key = 5; _key < _len; _key++) {
+                               args[_key - 5] = arguments[_key];
                        }
 
-                       ret = originalWindowOnError.call.apply(originalWindowOnError, [this, message, fileName, lineNumber].concat(args));
+                       ret = originalWindowOnError.call.apply(originalWindowOnError, [this, message, fileName, lineNumber, columnNumber, errorObj].concat(args));
                }
 
                // Treat return value as window.onerror itself does,
                                lineNumber: lineNumber
                        };
 
+                       // According to
+                       // https://blog.sentry.io/2016/01/04/client-javascript-reporting-window-onerror,
+                       // most modern browsers support an errorObj argument; use that to
+                       // get a full stack trace if it's available.
+                       if (errorObj && errorObj.stack) {
+                               error.stacktrace = extractStacktrace(errorObj, 0);
+                       }
+
                        ret = QUnit.onError(error);
                }
 
        };
 
        // Listen for unhandled rejections, and call QUnit.onUnhandledRejection
-       window.addEventListener("unhandledrejection", function (event) {
+       window$1.addEventListener("unhandledrejection", function (event) {
                QUnit.onUnhandledRejection(event.reason);
        });
   })();
index 2b67568..4882e9e 100644 (file)
                                        }
 
                                // Cancel selection
-                               } ).mousedown( function () {
+                               } ).on( 'mousedown', function () {
                                        if ( config.cancelSelection ) {
                                                this.onselectstart = function () {
                                                        return false;
index 63cf28c..435e23f 100644 (file)
@@ -14,7 +14,7 @@
                var prevCheckbox = null,
                        $box = this;
                // When our boxes are clicked..
-               $box.click( function ( e ) {
+               $box.on( 'click', function ( e ) {
                        // And one has been clicked before...
                        if ( prevCheckbox !== null && e.shiftKey ) {
                                // Check or uncheck this one and all in-between checkboxes,
index 09306f6..f5f0475 100644 (file)
@@ -82,6 +82,7 @@
                        }
                }
 
+               // eslint-disable-next-line jquery/no-animate-toggle
                $containers.toggle( action === 'expand' );
                hookCallback();
        }
index 1016e72..f2251f5 100644 (file)
                                // Only fetch if the value in the textbox changed and is not empty, or if the results were hidden
                                // if the textbox is empty then clear the result div, but leave other settings intouched
                                if ( val.length === 0 ) {
+                                       // eslint-disable-next-line jquery/no-animate-toggle
                                        $.suggestions.hide( context );
                                        context.data.prevText = '';
                                } else if (
                                        if ( context.data !== undefined ) {
                                                if ( context.data.$textbox.val().length === 0 ) {
                                                        // Hide the div when no suggestion exist
+                                                       // eslint-disable-next-line jquery/no-animate-toggle
                                                        $.suggestions.hide( context );
                                                } else {
                                                        // Rebuild the suggestions list
                                                                        .addClass( 'suggestions-result' )
                                                                        .attr( 'rel', i )
                                                                        .data( 'text', context.config.suggestions[ i ] )
-                                                                       .mousemove( function () {
+                                                                       .on( 'mousemove', function () {
                                                                                context.data.selectedWithMouse = true;
                                                                                $.suggestions.highlight(
                                                                                        context,
                                        break;
                                // Escape
                                case 27:
+                                       // eslint-disable-next-line jquery/no-animate-toggle
                                        $.suggestions.hide( context );
                                        $.suggestions.restore( context );
                                        $.suggestions.cancel( context );
                                case 13:
                                        preventDefault = wasVisible;
                                        selected = context.data.$container.find( '.suggestions-result-current' );
+                                       // eslint-disable-next-line jquery/no-animate-toggle
                                        $.suggestions.hide( context );
                                        if ( selected.length === 0 || context.data.selectedWithMouse ) {
                                                // If nothing is selected or if something was selected with the mouse
                                                        // Can't use click() because the container div is hidden when the
                                                        // textbox loses focus. Instead, listen for a mousedown followed
                                                        // by a mouseup on the same div.
-                                                       .mousedown( function ( e ) {
+                                                       .on( 'mousedown', function ( e ) {
                                                                context.data.mouseDownOn = $( e.target ).closest( '.suggestions-results .suggestions-result' );
                                                        } )
-                                                       .mouseup( function ( e ) {
+                                                       .on( 'mouseup', function ( e ) {
                                                                var $result = $( e.target ).closest( '.suggestions-results .suggestions-result' ),
                                                                        $other = context.data.mouseDownOn;
 
                                                                        // This will hide the link we're just clicking on, which causes problems
                                                                        // when done synchronously in at least Firefox 3.6 (T64858).
                                                                        setTimeout( function () {
+                                                                               // eslint-disable-next-line jquery/no-animate-toggle
                                                                                $.suggestions.hide( context );
-                                                                       }, 0 );
+                                                                       } );
                                                                }
                                                                // Always bring focus to the textbox, as that's probably where the user expects it
                                                                // if they were just typing.
                                                        // Can't use click() because the container div is hidden when the
                                                        // textbox loses focus. Instead, listen for a mousedown followed
                                                        // by a mouseup on the same div.
-                                                       .mousedown( function ( e ) {
+                                                       .on( 'mousedown', function ( e ) {
                                                                context.data.mouseDownOn = $( e.target ).closest( '.suggestions-special' );
                                                        } )
-                                                       .mouseup( function ( e ) {
+                                                       .on( 'mouseup', function ( e ) {
                                                                var $special = $( e.target ).closest( '.suggestions-special' ),
                                                                        $other = context.data.mouseDownOn;
 
                                                                        // This will hide the link we're just clicking on, which causes problems
                                                                        // when done synchronously in at least Firefox 3.6 (T64858).
                                                                        setTimeout( function () {
+                                                                               // eslint-disable-next-line jquery/no-animate-toggle
                                                                                $.suggestions.hide( context );
-                                                                       }, 0 );
+                                                                       } );
                                                                }
                                                                // Always bring focus to the textbox, as that's probably where the user expects it
                                                                // if they were just typing.
                                                                context.data.$textbox.focus();
                                                        } )
-                                                       .mousemove( function ( e ) {
+                                                       .on( 'mousemove', function ( e ) {
                                                                context.data.selectedWithMouse = true;
                                                                $.suggestions.highlight(
                                                                        context, $( e.target ).closest( '.suggestions-special' ), false
                                $( this )
                                        // Stop browser autocomplete from interfering
                                        .attr( 'autocomplete', 'off' )
-                                       .keydown( function ( e ) {
+                                       .on( 'keydown', function ( e ) {
                                                // Store key pressed to handle later
                                                context.data.keypressed = e.which;
                                                context.data.keypressedCount = 0;
                                        } )
-                                       .keypress( function ( e ) {
+                                       .on( 'keypress', function ( e ) {
                                                context.data.keypressedCount++;
+                                               // eslint-disable-next-line jquery/no-event-shorthand
                                                $.suggestions.keypress( e, context, context.data.keypressed );
                                        } )
-                                       .keyup( function ( e ) {
+                                       .on( 'keyup', function ( e ) {
                                                // The keypress event is fired when a key is pressed down and that key normally
                                                // produces a character value. We also want to handle some keys that don't
                                                // produce a character value so we also attach to the keydown/keyup events.
                                                        e.which === context.data.keypressed &&
                                                        allowed.indexOf( e.which ) !== -1
                                                ) {
+                                                       // eslint-disable-next-line jquery/no-event-shorthand
                                                        $.suggestions.keypress( e, context, context.data.keypressed );
                                                }
                                        } )
-                                       .blur( function () {
+                                       .on( 'blur', function () {
                                                // When losing focus because of a mousedown
                                                // on a suggestion, don't hide the suggestions
                                                if ( context.data.mouseDownOn.length > 0 ) {
                                                        return;
                                                }
+                                               // eslint-disable-next-line jquery/no-animate-toggle
                                                $.suggestions.hide( context );
                                                $.suggestions.cancel( context );
                                        } );
index 6b4ab97..82aa24f 100644 (file)
                                        }
 
                                        isSample = false;
-                                       $( this ).focus();
+                                       $( this ).trigger( 'focus' );
                                        if ( options.selectionStart !== undefined ) {
                                                $( this ).textSelection( 'setSelection', { start: options.selectionStart, end: options.selectionEnd } );
                                        }
index 4c4f5eb..cbfaf62 100644 (file)
@@ -34,7 +34,7 @@
                } );
 
                // Add form submission handler
-               $( '#editform' ).submit( function () {
+               $( '#editform' ).on( 'submit', function () {
                        allowCloseWindow.release();
                } );
        } );
index 966b5bc..a26da1d 100644 (file)
@@ -43,7 +43,7 @@
                        if ( scrollTop.value ) {
                                editBox.scrollTop = scrollTop.value;
                        }
-                       $editForm.submit( function () {
+                       $editForm.on( 'submit', function () {
                                scrollTop.value = editBox.scrollTop;
                        } );
                }
index 363b494..e907a98 100644 (file)
@@ -73,6 +73,8 @@
 
                // Can't use fadeTo because it calls show(), and we might want to keep some elements hidden
                // (e.g. empty #catlinks)
+               // FIXME: Use CSS transition
+               // eslint-disable-next-line jquery/no-animate
                $copyElements.animate( { opacity: 0.4 }, 'fast' );
 
                api = new mw.Api();
                                                        .append( $( '<a>' )
                                                                .attr( {
                                                                        href: mw.util.getUrl( template.title ),
-                                                                       'class': ( template.exists ? '' : 'new' )
+                                                                       class: ( template.exists ? '' : 'new' )
                                                                } )
                                                                .text( template.title )
                                                        );
                        mw.hook( 'wikipage.editform' ).fire( $editform );
                } ).always( function () {
                        $spinner.hide();
+                       // FIXME: Use CSS transition
+                       // eslint-disable-next-line jquery/no-animate
                        $copyElements.animate( {
                                opacity: 1
                        }, 'fast' );
                if ( !document.getElementById( 'p-lang' ) && document.getElementById( 'p-tb' ) && mw.config.get( 'skin' ) === 'vector' ) {
                        $( '.portal:last' ).after(
                                $( '<div>' ).attr( {
-                                       'class': 'portal',
+                                       class: 'portal',
                                        id: 'p-lang',
                                        role: 'navigation',
                                        'aria-labelledby': 'p-lang-label'
index a0bba6f..e638108 100644 (file)
@@ -51,7 +51,7 @@ $( function () {
                return true;
        }
 
-       $lis.find( 'input[name="diff"], input[name="oldid"]' ).click( updateDiffRadios );
+       $lis.find( 'input[name="diff"], input[name="oldid"]' ).on( 'click', updateDiffRadios );
 
        // Set initial state
        updateDiffRadios();
@@ -61,7 +61,7 @@ $( function () {
 
        // Ideally we'd use e.target instead of $historySubmitter, but e.target points
        // to the form element for submit actions, so.
-       $historyCompareForm.find( '.historysubmit' ).click( function () {
+       $historyCompareForm.find( '.historysubmit' ).on( 'click', function () {
                $historySubmitter = $( this );
        } );
 
@@ -71,7 +71,7 @@ $( function () {
        // Without the cloning we'd be changing the real form, which is slower, could make
        // the page look broken for a second in slow browsers and might show the form broken
        // again when coming back from a "next" page.
-       $historyCompareForm.submit( function ( e ) {
+       $historyCompareForm.on( 'submit', function ( e ) {
                var $copyForm, $copyRadios, $copyAction;
 
                if ( $historySubmitter ) {
@@ -92,14 +92,14 @@ $( function () {
                                $copyForm.find( ':submit' ).remove();
                        }
 
-                       // IE7 doesn't do submission from an off-DOM clone, so insert hidden into document first
+                       // Firefox requires the form to be attached, so insert hidden into document first
                        // Also remove potentially conflicting id attributes that we don't need anyway
                        $copyForm
                                .css( 'display', 'none' )
                                .find( '[id]' ).removeAttr( 'id' )
                                .end()
                                .insertAfter( $historyCompareForm )
-                               .submit();
+                               .trigger( 'submit' );
 
                        e.preventDefault();
                        return false; // Because the submit is special, return false as well.
index 4b0e49b..e359416 100644 (file)
@@ -12,6 +12,7 @@
                                $a = $( '#ca-edit a' );
                                // Not every page has an edit link (T59713)
                                if ( $a.length ) {
+                                       // eslint-disable-next-line jquery/no-event-shorthand
                                        $a.get( 0 ).click();
                                }
                        }
index 68b5214..c5dcd36 100644 (file)
@@ -54,7 +54,7 @@
                }
 
                $popup = $( '<div>' ).addClass( 'postedit mw-notification' ).append( $content )
-                       .click( function () {
+                       .on( 'click', function () {
                                clearTimeout( timeoutId );
                                fadeOutConfirmation();
                        } );
index 59d1d4f..e3f96b1 100644 (file)
@@ -21,6 +21,7 @@
                if ( e.target.nodeName.toLowerCase() !== 'a' ) {
                        // Trigger native HTMLElement click instead of opening URL (T45052)
                        e.preventDefault();
+                       // eslint-disable-next-line jquery/no-event-shorthand
                        $edit.get( 0 ).click();
                }
        } );
index 8d4b2bc..e5d0574 100644 (file)
                                        }
                                } );
                                tokenPromise.done( function () {
-                                       $form.submit();
+                                       $form.trigger( 'submit' );
                                } );
                        } );
 
index a89293d..f0fbdb6 100644 (file)
                        $checkboxes.prop( 'checked', check );
                }
 
-               $( '.mw-checkbox-all' ).click( function () {
+               $( '.mw-checkbox-all' ).on( 'click', function () {
                        selectAll( true );
                } );
-               $( '.mw-checkbox-none' ).click( function () {
+               $( '.mw-checkbox-none' ).on( 'click', function () {
                        selectAll( false );
                } );
-               $( '.mw-checkbox-invert' ).click( function () {
+               $( '.mw-checkbox-invert' ).on( 'click', function () {
                        $checkboxes.prop( 'checked', function ( i, val ) {
                                return !val;
                        } );
index 32dbdc1..189363f 100644 (file)
@@ -55,7 +55,7 @@
                        hovzer.$.append( this.$container );
                        hovzer.update();
 
-                       $( '.mw-debug-panelink' ).click( this.switchPane );
+                       $( '.mw-debug-panelink' ).on( 'click', this.switchPane );
                },
 
                /**
@@ -90,6 +90,8 @@
 
                        // Hide the current pane
                        if ( requestedPaneId === currentPaneId ) {
+                               // FIXME: Use CSS transition
+                               // eslint-disable-next-line jquery/no-slide
                                $currentPane.slideUp( updateHov );
                                debug.$container.data( 'currentPane', null );
                                return;
                        debug.$container.data( 'currentPane', requestedPaneId );
 
                        if ( currentPaneId === undefined || currentPaneId === null ) {
+                               // FIXME: Use CSS transition
+                               // eslint-disable-next-line jquery/no-slide
                                $requestedPane.slideDown( updateHov );
                        } else {
                                $currentPane.hide();
index 572d830..81bf1dd 100644 (file)
@@ -62,6 +62,6 @@
 
                // Override toggle handler because we don't need it for this popup
                // object at all. Sort of nasty, but it gets the job done.
-               dialog.getPopup().toggle = $.noop;
+               dialog.getPopup().toggle = function () {};
        }
 }() );
index beecfea..661a1c4 100644 (file)
                        $errorBox = this.$errorBox;
 
                if ( errors.length === 0 ) {
+                       // FIXME: Use CSS transition
+                       // eslint-disable-next-line jquery/no-slide
                        $errorBox.slideUp( function () {
                                $errorBox
                                        .removeAttr( 'class' )
                                                .removeAttr( 'class' )
                                                .detach();
                                }
+                               // FIXME: Use CSS transition
+                               // eslint-disable-next-line jquery/no-slide
                                $errorBox
                                        .attr( 'class', 'error' )
                                        .empty()
                                        .slideDown();
                        };
                        if ( $oldErrorBox !== $errorBox && $oldErrorBox.hasClass( 'error' ) ) {
+                               // eslint-disable-next-line jquery/no-slide
                                $oldErrorBox.slideUp( showFunc );
                        } else {
                                showFunc();
index 6e33856..8ead7a4 100644 (file)
@@ -43,8 +43,8 @@
                                        deleteButton.$element.closest( 'li.mw-htmlform-cloner-li' ).remove();
                                } );
                        } else {
-                               $element.filter( ':input' ).click( function ( ev ) {
-                                       ev.preventDefault();
+                               $element.filter( ':input' ).on( 'click', function ( e ) {
+                                       e.preventDefault();
                                        $( this ).closest( 'li.mw-htmlform-cloner-li' ).remove();
                                } );
                        }
@@ -56,8 +56,8 @@
                                appendToCloner( createButton.$element );
                        } );
                } else {
-                       $createElement.filter( ':input' ).click( function ( ev ) {
-                               ev.preventDefault();
+                       $createElement.filter( ':input' ).on( 'click', function ( e ) {
+                               e.preventDefault();
 
                                appendToCloner( $( this ) );
                        } );
index ab11f85..a778902 100644 (file)
@@ -14,7 +14,7 @@
                        name: name,
                        multiple: 'multiple',
                        'data-placeholder': dataPlaceholder.plain(),
-                       'class': 'htmlform-chzn-select mw-input ' + oldClass
+                       class: 'htmlform-chzn-select mw-input ' + oldClass
                } );
                $oldContainer.find( 'input' ).each( function () {
                        var $oldInput = $( this ),
index 7a643a5..da90c14 100644 (file)
@@ -41,7 +41,7 @@
                                        // cache the current selection to avoid expensive lookup
                                        currentValReasonList = $reasonList.val();
 
-                                       $reasonList.change( function () {
+                                       $reasonList.on( 'change', function () {
                                                currentValReasonList = $reasonList.val();
                                        } );
 
index 540de23..fdc988b 100644 (file)
                 * @param {Array} nodes List of nodes
                 * @return {string} Other message
                 */
-               'int': function ( nodes ) {
+               int: function ( nodes ) {
                        var msg = textify( nodes[ 0 ] );
                        return mw.jqueryMsg.getMessageFunction()( msg.charAt( 0 ).toLowerCase() + msg.slice( 1 ) );
                },
index 2da5122..73978d9 100644 (file)
                        }
 
                        if ( mw.config.get( 'wgCascadeableLevels' ) !== undefined ) {
-                               $( 'form#mw-Protect-Form' ).submit( this.toggleUnchainedInputs.bind( ProtectionForm, true ) );
+                               $( 'form#mw-Protect-Form' ).on( 'submit', this.toggleUnchainedInputs.bind( ProtectionForm, true ) );
                        }
                        this.getExpirySelectors().each( function () {
-                               $( this ).change( ProtectionForm.updateExpiryList.bind( ProtectionForm, this ) );
+                               $( this ).on( 'change', ProtectionForm.updateExpiryList.bind( ProtectionForm, this ) );
                        } );
                        this.getExpiryInputs().each( function () {
                                $( this ).on( 'keyup change', ProtectionForm.updateExpiry.bind( ProtectionForm, this ) );
                        } );
                        this.getLevelSelectors().each( function () {
-                               $( this ).change( ProtectionForm.updateLevels.bind( ProtectionForm, this ) );
+                               $( this ).on( 'change', ProtectionForm.updateLevels.bind( ProtectionForm, this ) );
                        } );
 
                        $( '#mwProtectSet > tbody > tr:first' ).after( $row );
@@ -38,7 +38,7 @@
                                $cell.append(
                                        $( '<input>' )
                                                .attr( { id: 'mwProtectUnchained', type: 'checkbox' } )
-                                               .click( this.onChainClick.bind( this ) )
+                                               .on( 'click', this.onChainClick.bind( this ) )
                                                .prop( 'checked', !this.areAllTypesMatching() ),
                                        document.createTextNode( ' ' ),
                                        $( '<label>' )
index f72fc92..2765d07 100644 (file)
                 * @property {Object}
                 */
                autoHideSeconds: {
-                       'short': 5,
-                       'long': 30
+                       short: 5,
+                       long: 30
                },
 
                /**
index 3f35c4b..0ffc867 100644 (file)
                        if ( !bound ) {
                                bound = true;
                                $( window )
-                                       .resize( $.debounce( 300, true, handleResizeStart ) )
-                                       .resize( $.debounce( 300, handleResizeEnd ) );
+                                       .on( 'resize', $.debounce( 300, true, handleResizeStart ) )
+                                       .on( 'resize', $.debounce( 300, handleResizeEnd ) );
                        }
                } );
        } );
index 71e7f80..12009d1 100644 (file)
@@ -49,7 +49,7 @@
                        mw.hook( 'wikipage.categories' ).fire( $nodes );
                }
 
-               $( '#t-print a' ).click( function ( e ) {
+               $( '#t-print a' ).on( 'click', function ( e ) {
                        window.print();
                        e.preventDefault();
                } );
index f7fbeef..c56aada 100644 (file)
                        $links = $links.filter( ':not( #bodyContent *, #content * )' );
                }
 
-               $links.click( function ( e ) {
+               $links.on( 'click', function ( e ) {
                        var mwTitle, action, api, $link;
 
                        mwTitle = mw.Title.newFromText( title );
index 36575f7..5d51d10 100644 (file)
 
                // Collect views
                allViews = $.extend( true, {
-                       'default': {
+                       default: {
                                title: mw.msg( 'rcfilters-filterlist-title' ),
                                groups: filterGroups
                        }
index b01aa7a..adf3fbb 100644 (file)
                                                id,
                                                obj.label,
                                                normalizedData,
-                                               { 'default': isDefault }
+                                               { default: isDefault }
                                        )
                                ] );
 
                                randomID,
                                label,
                                normalizedData,
-                               { 'default': isDefault }
+                               { default: isDefault }
                        )
                ] );
 
index 7565a0d..1651432 100644 (file)
@@ -90,7 +90,7 @@
                                        name: 'namespace', // parameter name is singular
                                        type: 'string_options',
                                        title: mw.msg( 'namespaces' ),
-                                       labelPrefixKey: { 'default': 'rcfilters-tag-prefix-namespace', inverted: 'rcfilters-tag-prefix-namespace-inverted' },
+                                       labelPrefixKey: { default: 'rcfilters-tag-prefix-namespace', inverted: 'rcfilters-tag-prefix-namespace-inverted' },
                                        separator: ';',
                                        fullCoverage: true,
                                        filters: items
                                                hidden: true,
                                                filters: [ {
                                                        name: 'invert',
-                                                       'default': '0'
+                                                       default: '0'
                                                } ]
                                        } ]
                        };
                                                max: 1000
                                        },
                                        sortFunc: function ( a, b ) { return Number( a.name ) - Number( b.name ); },
-                                       'default': mw.user.options.get( this.limitPreferenceName, displayConfig.limitDefault ),
+                                       default: mw.user.options.get( this.limitPreferenceName, displayConfig.limitDefault ),
                                        sticky: true,
                                        filters: displayConfig.limitArray.map( function ( num ) {
                                                return controller._createFilterDataFromNumber( num, num );
                                                        ( Number( i ) * 24 ).toFixed( 2 ) :
                                                        Number( i );
                                        },
-                                       'default': mw.user.options.get( this.daysPreferenceName, displayConfig.daysDefault ),
+                                       default: mw.user.options.get( this.daysPreferenceName, displayConfig.daysDefault ),
                                        sticky: true,
                                        filters: [
                                                // Hours (1, 2, 6, 12)
                                        filters: [
                                                {
                                                        name: 'enhanced',
-                                                       'default': String( mw.user.options.get( 'usenewrc', 0 ) )
+                                                       default: String( mw.user.options.get( 'usenewrc', 0 ) )
                                                }
                                        ]
                                }
index 2adfa7d..f866aa4 100644 (file)
@@ -52,7 +52,7 @@
                                                        filters: [
                                                                {
                                                                        name: 'target',
-                                                                       'default': ''
+                                                                       default: ''
                                                                }
                                                        ]
                                                },
@@ -65,7 +65,7 @@
                                                        filters: [
                                                                {
                                                                        name: 'showlinkedto',
-                                                                       'default': false
+                                                                       default: false
                                                                }
                                                        ]
                                                }
index 3c3f261..b76078e 100644 (file)
                        $firstNew.after( $indicator );
                }
 
+               // FIXME: Use CSS transition
+               // eslint-disable-next-line jquery/no-fade
                $newChanges
                        .hide()
                        .fadeIn( 1000 );
index 5e5cd6e..d0cc117 100644 (file)
                        this.queriesModel.connect( this, {
                                itemUpdate: 'onSavedQueriesItemUpdate',
                                initialize: 'onSavedQueriesInitialize',
-                               'default': 'reevaluateResetRestoreState'
+                               default: 'reevaluateResetRestoreState'
                        } );
                }
 
                                )
                        )
                ) {
+                       // eslint-disable-next-line jquery/no-animate
                        $( container ).animate( {
                                scrollTop: newScrollTop
                        } );
index 034a1c9..b4ec781 100644 (file)
@@ -40,8 +40,8 @@
                this.menu = new mw.rcfilters.ui.GroupWidget( {
                        events: {
                                click: 'menuItemClick',
-                               'delete': 'menuItemDelete',
-                               'default': 'menuItemDefault',
+                               delete: 'menuItemDelete',
+                               default: 'menuItemDefault',
                                edit: 'menuItemEdit'
                        },
                        classes: [ 'mw-rcfilters-ui-savedLinksListWidget-menu' ],
index 3762ae7..83a39d4 100644 (file)
                        // OO.ui.ButtonWidget doesn't take focus itself (T128054)
                        $focus = $( '#mw-apisandbox-ui' ).find( document.activeElement );
                        if ( $focus.length ) {
+                               // eslint-disable-next-line jquery/no-event-shorthand
                                $focus[ 0 ].blur();
                        }
 
                // it makes it too hard to read and our "disabled"
                // isn't really disabled.
                widgetField.onFieldDisable( false );
-               widgetField.onFieldDisable = $.noop;
+               widgetField.onFieldDisable = function () {};
 
                widgetField.apiParamIndex = ppi.index;
 
                                widget = Util.createWidgetForParameter( {
                                        name: name,
                                        type: 'string',
-                                       'default': ''
+                                       default: ''
                                }, {
                                        nooptional: true
                                } );
                                }
 
                                that.deprecatedItemsFieldset = new OO.ui.FieldsetLayout().addItems( deprecatedItems ).toggle( false );
+                               // eslint-disable-next-line jquery/no-animate-toggle
                                tmp = $( '<fieldset>' )
                                        .toggle( !that.deprecatedItemsFieldset.isEmpty() )
                                        .append(
index 89bbbc8..cd50369 100644 (file)
@@ -19,6 +19,7 @@
                        hideUserField = infuseIfExists( $( '#mw-input-wpHideUser' ).closest( '.oo-ui-fieldLayout' ) ),
                        watchUserField = infuseIfExists( $( '#mw-input-wpWatch' ).closest( '.oo-ui-fieldLayout' ) ),
                        expiryWidget = infuseIfExists( $( '#mw-input-wpExpiry' ) ),
+                       editingWidget = infuseIfExists( $( '#mw-input-wpEditing' ) ),
                        editingRestrictionWidget = infuseIfExists( $( '#mw-input-wpEditingRestriction' ) ),
                        preventTalkPageEdit = infuseIfExists( $( '#mw-input-wpDisableUTEdit' ) ),
                        pageRestrictionsWidget = infuseIfExists( $( '#mw-input-wpPageRestrictions' ) );
@@ -33,7 +34,8 @@
                                // infinityValues  are the values the SpecialBlock class accepts as infinity (sf. wfIsInfinity)
                                infinityValues = [ 'infinite', 'indefinite', 'infinity', 'never' ],
                                isIndefinite = infinityValues.indexOf( expiryValue ) !== -1,
-                               editingRestrictionValue = editingRestrictionWidget ? editingRestrictionWidget.getValue() : undefined;
+                               editingRestrictionValue = editingRestrictionWidget ? editingRestrictionWidget.getValue() : undefined,
+                               editingIsSelected = editingWidget ? editingWidget.isSelected() : undefined;
 
                        if ( enableAutoblockField ) {
                                enableAutoblockField.toggle( !( isNonEmptyIp ) );
                                watchUserField.toggle( !( isIpRange && !isEmpty ) );
                        }
                        if ( pageRestrictionsWidget ) {
-                               pageRestrictionsWidget.setDisabled( editingRestrictionValue === 'sitewide' );
+                               editingRestrictionWidget.setDisabled( !editingIsSelected );
+                               pageRestrictionsWidget.setDisabled( !editingIsSelected || editingRestrictionValue === 'sitewide' );
                        }
                        if ( preventTalkPageEdit ) {
                                // TODO: (T210475) this option is disabled for partial blocks unless
                                // a namespace restriction for User_talk namespace is in place.
                                // This needs to be updated once Namespace restrictions is available
-                               if ( editingRestrictionValue === 'partial' ) {
+                               if ( editingRestrictionValue === 'partial' && editingIsSelected ) {
                                        preventTalkPageEdit.setDisabled( true );
                                } else {
                                        preventTalkPageEdit.setDisabled( false );
@@ -70,6 +73,9 @@
                        if ( editingRestrictionWidget ) {
                                editingRestrictionWidget.on( 'change', updateBlockOptions );
                        }
+                       if ( editingWidget ) {
+                               editingWidget.on( 'change', updateBlockOptions );
+                       }
 
                        // Call them now to set initial state (ie. Special:Block/Foobar?wpBlockExpiry=2+hours)
                        updateBlockOptions();
index ba34eb3..bc8ca37 100644 (file)
@@ -30,7 +30,7 @@
        $( function () {
                var $projectField = $( '#mw-import-table-interwiki #interwiki' );
                if ( $projectField.length ) {
-                       $projectField.change( updateImportSubprojectList );
+                       $projectField.on( 'change', updateImportSubprojectList );
                        updateImportSubprojectList();
                }
        } );
index d8f73b2..8885883 100644 (file)
@@ -27,7 +27,7 @@
 
                        // Bind to change event, and trigger once to set the initial state of the checkboxes.
                        rc.updateCheckboxes();
-                       $select.change( rc.updateCheckboxes );
+                       $select.on( 'change', rc.updateCheckboxes );
                }
        };
 
index 8e6d160..ae4cd55 100644 (file)
 
                for ( i = 0; i < results.length; i++ ) {
                        result = results[ i ];
-                       imageCaption = mw.html.element( 'span', { 'class': 'iw-result__mini-gallery__caption' }, result.title );
+                       imageCaption = mw.html.element( 'span', { class: 'iw-result__mini-gallery__caption' }, result.title );
                        imageThumbnailSrc = ( result.thumbnail ) ? result.thumbnail.source : '';
                        resultOutput += '<div class="iw-result__mini-gallery">' +
                                                /* escaping response content */
                                                mw.html.element( 'a', {
                                                        href: '/wiki/' + result.title,
-                                                       'class': 'iw-result__mini-gallery__image',
+                                                       class: 'iw-result__mini-gallery__image',
                                                        style: 'background-image: url(' + imageThumbnailSrc + ');'
                                                }, new mw.html.Raw( imageCaption ) ) +
                                        '</div>';
index d5760b2..02ac862 100644 (file)
@@ -7,15 +7,15 @@
 
                // Emulate HTML5 autofocus behavior in non HTML5 compliant browsers
                if ( !( 'autofocus' in document.createElement( 'input' ) ) ) {
-                       $( 'input[autofocus]' ).eq( 0 ).focus();
+                       $( 'input[autofocus]' ).eq( 0 ).trigger( 'focus' );
                }
 
                // Attach handler for check all/none buttons
                $checkboxes = $( '#powersearch input[id^=mw-search-ns]' );
-               $( '#mw-search-toggleall' ).click( function () {
+               $( '#mw-search-toggleall' ).on( 'click', function () {
                        $checkboxes.prop( 'checked', true );
                } );
-               $( '#mw-search-togglenone' ).click( function () {
+               $( '#mw-search-togglenone' ).on( 'click', function () {
                        $checkboxes.prop( 'checked', false );
                } );
 
@@ -39,7 +39,7 @@
                updateHeaderLinks( searchWidget.getValue() );
 
                // When saving settings, use the proper request method (POST instead of GET).
-               $( '#mw-search-powersearch-remember' ).change( function () {
+               $( '#mw-search-powersearch-remember' ).on( 'change', function () {
                        this.form.method = this.checked ? 'post' : 'get';
                } ).trigger( 'change' );
 
index e8eb870..92a5987 100644 (file)
@@ -7,7 +7,7 @@
                        summaryByteLimit = mw.config.get( 'wgCommentByteLimit' ),
                        wpComment = OO.ui.infuse( $( '#wpComment' ).closest( '.oo-ui-widget' ) );
 
-               $( '#mw-undelete-invert' ).click( function () {
+               $( '#mw-undelete-invert' ).on( 'click', function () {
                        $( '.mw-undelete-revlist input[type="checkbox"]' ).prop( 'checked', function ( i, val ) {
                                return !val;
                        } );
index 7783ff7..53617ca 100644 (file)
@@ -3,7 +3,7 @@
  */
 ( function () {
        $( function () {
-               $( 'a.mw-watch-link' ).click( function ( e ) {
+               $( 'a.mw-watch-link' ).on( 'click', function ( e ) {
                        var promise,
                                api = new mw.Api(),
                                $link = $( this ),
index bfb4c5e..8abb8f2 100644 (file)
                if ( ajaxUploadDestCheck ) {
                        // Insert an event handler that fetches upload warnings when wpDestFile
                        // has been changed
-                       $( '#wpDestFile' ).change( function () {
+                       $( '#wpDestFile' ).on( 'change', function () {
                                uploadWarning.checkNow( $( this ).val() );
                        } );
                        // Insert a row where the warnings will be displayed just below the
 
                if ( mw.config.get( 'wgAjaxLicensePreview' ) && $license.length ) {
                        // License selector check
-                       $license.change( function () {
+                       $license.on( 'change', function () {
                                // We might show a preview
                                uploadTemplatePreview.getPreview( $license, $( '#mw-license-preview' ) );
                        } );
 
                // fillDestFile setup
                mw.config.get( 'wgUploadSourceIds' ).forEach( function ( sourceId ) {
-                       $( '#' + sourceId ).change( function () {
+                       $( '#' + sourceId ).on( 'change', function () {
                                var path, slash, backslash, fname;
                                if ( !mw.config.get( 'wgUploadAutoFill' ) ) {
                                        return;
                /* Initialization */
                if ( hasFileAPI() ) {
                        // Update thumbnail when the file selection control is updated.
-                       $( '#wpUploadFile' ).change( function () {
+                       $( '#wpUploadFile' ).on( 'change', function () {
                                var file;
                                clearPreview();
                                if ( this.files && this.files.length ) {
                        namespace: 'uploadwarning'
                } );
 
-               $uploadForm.submit( function () {
+               $uploadForm.on( 'submit', function () {
                        allowCloseWindow.release();
                } );
        } );
 
                        // Change tabindex only when main div has focus
                        if ( $( this ).is( ':focus' ) ) {
-                               $( this ).find( 'a' ).first().focus();
+                               $( this ).find( 'a' ).first().trigger( 'focus' );
                                setEditTabindex( '0' );
                        }
                } );
index a4f5d1a..63d9623 100644 (file)
@@ -12,6 +12,7 @@
 
        // Dynamically show/hide the "other time" input under each dropdown
        $( '.mw-userrights-nested select' ).on( 'change', function ( e ) {
+               // eslint-disable-next-line jquery/no-animate-toggle
                $( e.target.parentNode ).find( 'input' ).toggle( $( e.target ).val() === 'other' );
        } );
 
index b9be51f..2d60b1d 100644 (file)
@@ -8,7 +8,7 @@
                // If the user wants to reset their watchlist, use an API call to do so (no reload required)
                // Adapted from a user script by User:NQ of English Wikipedia
                // (User:NQ/WatchlistResetConfirm.js)
-               $resetForm.submit( function ( event ) {
+               $resetForm.on( 'submit', function ( event ) {
                        var $button = $resetForm.find( 'input[name=mw-watchlist-reset-submit]' );
 
                        event.preventDefault();
@@ -41,7 +41,7 @@
                        } ).fail( function () {
                                // On error, fall back to server-side reset
                                // First remove this submit listener and then re-submit the form
-                               $resetForm.off( 'submit' ).submit();
+                               $resetForm.off( 'submit' ).trigger( 'submit' );
                        } );
                } );
 
@@ -50,7 +50,7 @@
                        // add a listener on all form elements in the header form
                        $( '#mw-watchlist-form input, #mw-watchlist-form select' ).on( 'change', function () {
                                // submit the form when one of the input fields is modified
-                               $( '#mw-watchlist-form' ).submit();
+                               $( '#mw-watchlist-form' ).trigger( 'submit' );
                        } );
                }
 
 
                                event.preventDefault();
                                event.stopPropagation();
-                               $unwatchLink.blur();
+                               $unwatchLink.trigger( 'blur' );
                        } );
                }
        } );
index c4f0a5e..30cafe5 100644 (file)
@@ -6,11 +6,24 @@
 
 @ooui-spacing-radio-label: 26 / @ooui-font-size-browser / @ooui-font-size-base; // Equals `1.85714286em`≈`26px`
 
-.mw-block-page-restrictions.oo-ui-fieldLayout {
-       margin-top: 0;
-       margin-left: @ooui-spacing-radio-label;
+body.mw-special-Block {
+       .mw-block-editing-restriction.oo-ui-fieldLayout {
+               margin-left: @ooui-spacing-radio-label;
+       }
+
+       .mw-block-page-restrictions.oo-ui-fieldLayout {
+               margin-left: 2 * @ooui-spacing-radio-label;
+
+               .oo-ui-widget {
+                       max-width: 50em - 2 * @ooui-spacing-radio-label;
+               }
+       }
+
+       .oo-ui-panelLayout-framed {
+               border: 0;
+       }
 
-       .oo-ui-widget {
-               max-width: 50em - @ooui-spacing-radio-label;
+       .oo-ui-panelLayout-padded {
+               padding: 0;
        }
 }
index de81d6a..85dbf97 100644 (file)
                        // Hide/show the table of contents element
                        function toggleToc() {
                                if ( $tocList.is( ':hidden' ) ) {
+                                       // FIXME: Use CSS transitions
+                                       // eslint-disable-next-line jquery/no-slide
                                        $tocList.slideDown( 'fast' );
                                        $tocToggleLink.text( mw.msg( 'hidetoc' ) );
                                        $this.removeClass( 'tochidden' );
                                        mw.cookie.set( 'hidetoc', null );
                                } else {
+                                       // eslint-disable-next-line jquery/no-slide
                                        $tocList.slideUp( 'fast' );
                                        $tocToggleLink.text( mw.msg( 'showtoc' ) );
                                        $this.addClass( 'tochidden' );
index 545bf40..f4ad578 100644 (file)
                                                );
                                        }
                                        if ( $field.is( ':input' ) ) {
-                                               $field.select();
+                                               $field.trigger( 'select' );
                                        }
                                        return false;
                        }
                                this.setValue( this.formatter.getDefaultDate() );
                        }
                        if ( $field.is( ':input' ) ) {
-                               $field.select();
+                               $field.trigger( 'select' );
                        }
 
                        if ( this.calendar ) {
index 86103d1..58646a9 100644 (file)
@@ -48,7 +48,7 @@
         *  are set up. Note: The promise must have an .abort() functionality.
         */
        mw.widgets.APIResultsQueue.prototype.setup = function () {
-               return $.Deferred().resolve().promise( { abort: $.noop } );
+               return $.Deferred().resolve().promise( { abort: function () {} } );
        };
 
        /**
index 23ee2c0..3002d45 100644 (file)
                        provider = this;
 
                if ( !this.isValid() ) {
-                       return $.Deferred().reject().promise( { abort: $.noop } );
+                       return $.Deferred().reject().promise( { abort: function () {} } );
                }
 
                api = this.isLocal ? new mw.Api() : new mw.ForeignApi( this.getAPIurl(), { anonymous: true } );
index c7502da..4d58f18 100644 (file)
                                if ( e.which === OO.ui.Keys.TAB ) {
                                        if ( e.shiftKey ) {
                                                // Tabbing backward from text input: normal browser behavior
-                                               $.noop();
                                        } else {
                                                // Tabbing forward from text input: just focus the calendar
                                                this.calendar.$element.focus();
index 76c6718..f1c0c6f 100644 (file)
                        self = this;
 
                // reuse the searchSuggest function from mw.searchSuggest
-               promise = mw.searchSuggest.request( api, this.getQueryValue(), $.noop, this.limit, this.getNamespace() );
+               promise = mw.searchSuggest.request( api, this.getQueryValue(), function () {}, this.limit, this.getNamespace() );
 
                // tracking purposes
                promise.done( function ( data, jqXHR ) {
index 11b9c01..cb5e1f8 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 
+use MediaWiki\Block\Restriction\PageRestriction;
 use MediaWiki\MediaWikiServices;
 
 /**
@@ -892,7 +893,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                        'wgEmailAuthentication' => true,
                ] );
 
-               $this->setUserPerm( [ "createpage", "move" ] );
+               $this->setUserPerm( [ 'createpage', 'edit', 'move', 'rollback', 'patrol', 'upload', 'purge' ] );
                $this->setTitle( NS_HELP, "test page" );
 
                # $wgEmailConfirmToEdit only applies to 'edit' action
@@ -964,11 +965,24 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                        'expiry' => 10,
                        'systemBlock' => 'test',
                ] );
-               $this->assertEquals( [ [ 'systemblockedtext',
+
+               $errors = [ [ 'systemblockedtext',
                                '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
                                'Useruser', 'test', '23:00, 31 December 1969', '127.0.8.1',
-                               $wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ],
+                               $wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ];
+
+               $this->assertEquals( $errors,
+                       $this->title->getUserPermissionsErrors( 'edit', $this->user ) );
+               $this->assertEquals( $errors,
                        $this->title->getUserPermissionsErrors( 'move-target', $this->user ) );
+               $this->assertEquals( $errors,
+                       $this->title->getUserPermissionsErrors( 'rollback', $this->user ) );
+               $this->assertEquals( $errors,
+                       $this->title->getUserPermissionsErrors( 'patrol', $this->user ) );
+               $this->assertEquals( $errors,
+                       $this->title->getUserPermissionsErrors( 'upload', $this->user ) );
+               $this->assertEquals( [],
+                       $this->title->getUserPermissionsErrors( 'purge', $this->user ) );
 
                // partial block message test
                $this->user->mBlockedby = $this->user->getName();
@@ -981,10 +995,39 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
                        'expiry' => 10,
                ] );
 
-               $this->assertEquals( [ [ 'blockedtext-partial',
+               $this->assertEquals( [],
+                       $this->title->getUserPermissionsErrors( 'edit', $this->user ) );
+               $this->assertEquals( [],
+                       $this->title->getUserPermissionsErrors( 'move-target', $this->user ) );
+               $this->assertEquals( [],
+                       $this->title->getUserPermissionsErrors( 'rollback', $this->user ) );
+               $this->assertEquals( [],
+                       $this->title->getUserPermissionsErrors( 'patrol', $this->user ) );
+               $this->assertEquals( [],
+                       $this->title->getUserPermissionsErrors( 'upload', $this->user ) );
+               $this->assertEquals( [],
+                       $this->title->getUserPermissionsErrors( 'purge', $this->user ) );
+
+               $this->user->mBlock->setRestrictions( [
+                               ( new PageRestriction( 0, $this->title->getArticleID() ) )->setTitle( $this->title ),
+               ] );
+
+               $errors = [ [ 'blockedtext-partial',
                                '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1',
                                'Useruser', null, '23:00, 31 December 1969', '127.0.8.1',
-                               $wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ],
+                               $wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ] ];
+
+               $this->assertEquals( $errors,
+                       $this->title->getUserPermissionsErrors( 'edit', $this->user ) );
+               $this->assertEquals( $errors,
                        $this->title->getUserPermissionsErrors( 'move-target', $this->user ) );
+               $this->assertEquals( $errors,
+                       $this->title->getUserPermissionsErrors( 'rollback', $this->user ) );
+               $this->assertEquals( $errors,
+                       $this->title->getUserPermissionsErrors( 'patrol', $this->user ) );
+               $this->assertEquals( [],
+                       $this->title->getUserPermissionsErrors( 'upload', $this->user ) );
+               $this->assertEquals( [],
+                       $this->title->getUserPermissionsErrors( 'purge', $this->user ) );
        }
 }
index 5f1b9fe..e67d405 100644 (file)
@@ -88,7 +88,7 @@ class AbstractPasswordPrimaryAuthenticationProviderTest extends \MediaWikiTestCa
 
        public function testCheckPasswordValidity() {
                $uppCalled = 0;
-               $uppStatus = \Status::newGood();
+               $uppStatus = \Status::newGood( [] );
                $this->setMwGlobals( [
                        'wgPasswordPolicy' => [
                                'policies' => [
index b7e86c8..e1b25a1 100644 (file)
@@ -174,6 +174,16 @@ class LocalPasswordPrimaryAuthenticationProviderTest extends \MediaWikiTestCase
                $this->assertNotNull( $ret );
                $this->assertSame( 'resetpass-validity-soft', $ret->msg->getKey() );
                $this->assertFalse( $ret->hard );
+
+               $this->manager->removeAuthenticationSessionData( null );
+               $row->user_password_expires = null;
+               $status = \Status::newGood( [ 'forceChange' => true ] );
+               $status->error( 'testing' );
+               $providerPriv->setPasswordResetFlag( $userName, $status, $row );
+               $ret = $this->manager->getAuthenticationSessionData( 'reset-pass' );
+               $this->assertNotNull( $ret );
+               $this->assertSame( 'resetpass-validity', $ret->msg->getKey() );
+               $this->assertTrue( $ret->hard );
        }
 
        public function testAuthentication() {
index 78175fa..80881ad 100644 (file)
@@ -30,7 +30,7 @@ class UserPasswordPolicyTest extends MediaWikiTestCase {
 
        protected $policies = [
                'checkuser' => [
-                       'MinimalPasswordLength' => 10,
+                       'MinimalPasswordLength' => [ 'value' => 10, 'forceChange' => true ],
                        'MinimumPasswordLengthToLogin' => 6,
                        'PasswordCannotMatchUsername' => true,
                ],
@@ -44,6 +44,8 @@ class UserPasswordPolicyTest extends MediaWikiTestCase {
                        'MinimumPasswordLengthToLogin' => 1,
                        'PasswordCannotMatchBlacklist' => true,
                        'MaximalPasswordLength' => 4096,
+                       // test null handling
+                       'PasswordCannotMatchUsername' => null,
                ],
        ];
 
@@ -62,15 +64,24 @@ class UserPasswordPolicyTest extends MediaWikiTestCase {
        public function testGetPoliciesForUser() {
                $upp = $this->getUserPasswordPolicy();
 
-               $user = User::newFromName( 'TestUserPolicy' );
-               $user->addToDatabase();
-               $user->addGroup( 'sysop' );
-
+               $user = $this->getTestUser( [ 'sysop' ] )->getUser();
                $this->assertArrayEquals(
                        [
                                'MinimalPasswordLength' => 8,
                                'MinimumPasswordLengthToLogin' => 1,
-                               'PasswordCannotMatchUsername' => 1,
+                               'PasswordCannotMatchUsername' => true,
+                               'PasswordCannotMatchBlacklist' => true,
+                               'MaximalPasswordLength' => 4096,
+                       ],
+                       $upp->getPoliciesForUser( $user )
+               );
+
+               $user = $this->getTestUser( [ 'sysop', 'checkuser' ] )->getUser();
+               $this->assertArrayEquals(
+                       [
+                               'MinimalPasswordLength' => [ 'value' => 10, 'forceChange' => true ],
+                               'MinimumPasswordLengthToLogin' => 6,
+                               'PasswordCannotMatchUsername' => true,
                                'PasswordCannotMatchBlacklist' => true,
                                'MaximalPasswordLength' => 4096,
                        ],
@@ -87,7 +98,7 @@ class UserPasswordPolicyTest extends MediaWikiTestCase {
 
                $this->assertArrayEquals(
                        [
-                               'MinimalPasswordLength' => 10,
+                               'MinimalPasswordLength' => [ 'value' => 10, 'forceChange' => true ],
                                'MinimumPasswordLengthToLogin' => 6,
                                'PasswordCannotMatchUsername' => true,
                                'PasswordCannotMatchBlacklist' => true,
@@ -100,108 +111,96 @@ class UserPasswordPolicyTest extends MediaWikiTestCase {
        /**
         * @dataProvider provideCheckUserPassword
         */
-       public function testCheckUserPassword( $username, $groups, $password, $valid, $ok, $msg ) {
+       public function testCheckUserPassword( $groups, $password, StatusValue $expectedStatus ) {
                $upp = $this->getUserPasswordPolicy();
-
-               $user = User::newFromName( $username );
-               $user->addToDatabase();
-               foreach ( $groups as $group ) {
-                       $user->addGroup( $group );
-               }
+               $user = $this->getTestUser( $groups )->getUser();
 
                $status = $upp->checkUserPassword( $user, $password );
-               $this->assertSame( $valid, $status->isGood(), $msg . ' - password valid' );
-               $this->assertSame( $ok, $status->isOK(), $msg . ' - can login' );
+               $this->assertSame( $expectedStatus->isGood(), $status->isGood(), 'password valid' );
+               $this->assertSame( $expectedStatus->isOK(), $status->isOK(), 'can login' );
+               $this->assertSame( $expectedStatus->getValue(), $status->getValue(), 'flags' );
        }
 
        public function provideCheckUserPassword() {
+               $success = Status::newGood( [] );
+               $warning = Status::newGood( [] );
+               $forceChange = Status::newGood( [ 'forceChange' => true ] );
+               $fatal = Status::newGood( [] );
+               // the message does not matter, we only test for state and value
+               $warning->warning( 'invalid-password' );
+               $forceChange->warning( 'invalid-password' );
+               $warning->warning( 'invalid-password' );
+               $fatal->fatal( 'invalid-password' );
                return [
-                       [
-                               'PassPolicyUser',
+                       'No groups, default policy, password too short to login' => [
                                [],
                                '',
-                               false,
-                               false,
-                               'No groups, default policy, password too short to login'
+                               $fatal,
                        ],
-                       [
-                               'PassPolicyUser',
+                       'Default policy, short password' => [
                                [ 'user' ],
                                'aaa',
-                               false,
-                               true,
-                               'Default policy, short password'
+                               $warning,
                        ],
-                       [
-                               'PassPolicyUser',
+                       'Sysop with good password' => [
                                [ 'sysop' ],
                                'abcdabcdabcd',
-                               true,
-                               true,
-                               'Sysop with good password'
+                               $success,
                        ],
-                       [
-                               'PassPolicyUser',
+                       'Sysop with short password' => [
                                [ 'sysop' ],
                                'abcd',
-                               false,
-                               true,
-                               'Sysop with short password'
+                               $warning,
                        ],
-                       [
-                               'PassPolicyUser',
+                       'Checkuser with short password' => [
                                [ 'sysop', 'checkuser' ],
                                'abcdabcd',
-                               false,
-                               true,
-                               'Checkuser with short password'
+                               $forceChange,
                        ],
-                       [
-                               'PassPolicyUser',
+                       'Checkuser with too short password to login' => [
                                [ 'sysop', 'checkuser' ],
                                'abcd',
-                               false,
-                               false,
-                               'Checkuser with too short password to login'
-                       ],
-                       [
-                               'Useruser',
-                               [ 'user' ],
-                               'Passpass',
-                               false,
-                               true,
-                               'Username & password on blacklist'
+                               $fatal,
                        ],
                ];
        }
 
+       public function testCheckUserPassword_blacklist() {
+               $upp = $this->getUserPasswordPolicy();
+               $user = User::newFromName( 'Useruser' );
+               $user->addToDatabase();
+
+               $status = $upp->checkUserPassword( $user, 'Passpass' );
+               $this->assertFalse( $status->isGood(), 'password invalid' );
+               $this->assertTrue( $status->isOK(), 'can login' );
+       }
+
        /**
         * @dataProvider provideMaxOfPolicies
         */
-       public function testMaxOfPolicies( $p1, $p2, $max, $msg ) {
+       public function testMaxOfPolicies( $p1, $p2, $max ) {
                $this->assertArrayEquals(
                        $max,
-                       UserPasswordPolicy::maxOfPolicies( $p1, $p2 ),
-                       $msg
+                       UserPasswordPolicy::maxOfPolicies( $p1, $p2 )
                );
        }
 
        public function provideMaxOfPolicies() {
                return [
-                       [
+                       'Basic max in p1' => [
                                [ 'MinimalPasswordLength' => 8 ], // p1
                                [ 'MinimalPasswordLength' => 2 ], // p2
                                [ 'MinimalPasswordLength' => 8 ], // max
-                               'Basic max in p1'
                        ],
-                       [
+                       'Basic max in p2' => [
                                [ 'MinimalPasswordLength' => 2 ], // p1
                                [ 'MinimalPasswordLength' => 8 ], // p2
                                [ 'MinimalPasswordLength' => 8 ], // max
-                               'Basic max in p2'
                        ],
-                       [
-                               [ 'MinimalPasswordLength' => 8 ], // p1
+                       'Missing items in p1' => [
+                               [
+                                       'MinimalPasswordLength' => 8,
+                               ], // p1
                                [
                                        'MinimalPasswordLength' => 2,
                                        'PasswordCannotMatchUsername' => 1,
@@ -210,9 +209,8 @@ class UserPasswordPolicyTest extends MediaWikiTestCase {
                                        'MinimalPasswordLength' => 8,
                                        'PasswordCannotMatchUsername' => 1,
                                ], // max
-                               'Missing items in p1'
                        ],
-                       [
+                       'Missing items in p2' => [
                                [
                                        'MinimalPasswordLength' => 8,
                                        'PasswordCannotMatchUsername' => 1,
@@ -224,7 +222,64 @@ class UserPasswordPolicyTest extends MediaWikiTestCase {
                                        'MinimalPasswordLength' => 8,
                                        'PasswordCannotMatchUsername' => 1,
                                ], // max
-                               'Missing items in p2'
+                       ],
+                       'complex value in p1' => [
+                               [
+                                       'MinimalPasswordLength' => [
+                                               'value' => 8,
+                                               'foo' => 1,
+                                       ],
+                               ], // p1
+                               [
+                                       'MinimalPasswordLength' => 2,
+                               ], // p2
+                               [
+                                       'MinimalPasswordLength' => [
+                                               'value' => 8,
+                                               'foo' => 1,
+                                       ],
+                               ], // max
+                       ],
+                       'complex value in p2' => [
+                               [
+                                       'MinimalPasswordLength' => 8,
+                               ], // p1
+                               [
+                                       'MinimalPasswordLength' => [
+                                               'value' => 2,
+                                               'foo' => 1,
+                                       ],
+                               ], // p2
+                               [
+                                       'MinimalPasswordLength' => [
+                                               'value' => 8,
+                                               'foo' => 1,
+                                       ],
+                               ], // max
+                       ],
+                       'complex value in both p1 and p2' => [
+                               [
+                                       'MinimalPasswordLength' => [
+                                               'value' => 8,
+                                               'foo' => 1,
+                                               'baz' => false,
+                                       ],
+                               ], // p1
+                               [
+                                       'MinimalPasswordLength' => [
+                                               'value' => 2,
+                                               'bar' => 2,
+                                               'baz' => true,
+                                       ],
+                               ], // p2
+                               [
+                                       'MinimalPasswordLength' => [
+                                               'value' => 8,
+                                               'foo' => 1,
+                                               'bar' => 2,
+                                               'baz' => true,
+                                       ],
+                               ], // max
                        ],
                ];
        }
index a80262e..58c69e3 100644 (file)
@@ -143,8 +143,8 @@ class UploadBaseTest extends MediaWikiTestCase {
                        // html5sec SVG vectors
                        [
                                '<svg xmlns="http://www.w3.org/2000/svg"><script>alert(1)</script></svg>',
-                               true,
-                               true,
+                               true, /* SVG is well formed */
+                               true, /* Evil SVG detected */
                                'Script tag in svg (http://html5sec.org/#47)'
                        ],
                        [
@@ -509,7 +509,20 @@ class UploadBaseTest extends MediaWikiTestCase {
                                true,
                                false,
                                'DTD with aliased entities apos (Should be allowed)'
-                       ]
+                       ],
+                       [
+                               '<svg xmlns="http://www.w3.org/2000/svg"><g filter="url( \'#foo\' )"></g></svg>',
+                               true,
+                               false,
+                               'SVG with local filter (T69044)'
+                       ],
+                       [
+                               '<svg xmlns="http://www.w3.org/2000/svg"><g filter="url( http://example.com/#foo )"></g></svg>',
+                               true,
+                               true,
+                               'SVG with non-local filter (T69044)'
+                       ],
+
                ];
                // phpcs:enable
        }
index 47f04b2..03b02ba 100644 (file)
@@ -7,10 +7,11 @@
                "sinon": false
        },
        "rules": {
-               "operator-linebreak": 0,
+               "operator-linebreak": "off",
                "quote-props": [ "error", "as-needed" ],
-               "valid-jsdoc": 0,
-               "qunit/require-expect": 0,
-               "qunit/resolve-async": 0
+               "valid-jsdoc": "off",
+               "qunit/require-expect": "off",
+               "qunit/resolve-async": "off",
+               "jquery/no-parse-html-literal": "off"
        }
 }
index 743660a..df3d61b 100644 (file)
                        if ( warn === undefined ) {
                                warn = mw.log.warn;
                                error = mw.log.error;
-                               mw.log.warn = mw.log.error = $.noop;
+                               mw.log.warn = mw.log.error = function () {};
                        }
                }
 
index d207260..2e35420 100644 (file)
@@ -5,6 +5,7 @@
                var done = assert.async(),
                        $canvas = $( '<div>' ).css( 'background-color', '#fff' ).appendTo( '#qunit-fixture' );
 
+               // eslint-disable-next-line jquery/no-animate
                $canvas.animate( { 'background-color': '#000' }, 3 ).promise()
                        .done( function () {
                                var endColors = $.colorUtil.getRGB( $canvas.css( 'background-color' ) );
index 360ef01..bd6ee16 100644 (file)
                                callback( $table );
                        } else {
                                $table.tablesorter();
-                               $table.find( '#sortme' ).click();
+                               $table.find( '#sortme' ).trigger( 'click' );
                        }
 
                        // Table sorting is done synchronously; if it ever needs to change back
                planetsAscName,
                function ( $table ) {
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).click();
+                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
                }
        );
        tableTest(
                planetsAscName,
                function ( $table ) {
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).click();
+                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
                }
        );
        tableTest(
                planetsAscName,
                function ( $table ) {
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).click();
-                       $table.find( '.headerSort:eq(1)' ).click();
-                       $table.find( '.headerSort:eq(0)' ).click();
+                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
+                       $table.find( '.headerSort:eq(1)' ).trigger( 'click' );
+                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
                }
        );
        tableTest(
                reversed( planetsAscName ),
                function ( $table ) {
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).click().click();
+                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' ).trigger( 'click' );
                }
        );
        tableTest(
                planetsAscRadius,
                function ( $table ) {
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(1)' ).click();
+                       $table.find( '.headerSort:eq(1)' ).trigger( 'click' );
                }
        );
        tableTest(
                reversed( planetsAscRadius ),
                function ( $table ) {
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(1)' ).click().click();
+                       $table.find( '.headerSort:eq(1)' ).trigger( 'click' ).trigger( 'click' );
                }
        );
        tableTest(
                        $table.tablesorter(
                                { sortList: [ { 0: 'asc' }, { 1: 'asc' } ] }
                        );
-                       $table.find( '.headerSort:eq(0)' ).click();
+                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
                }
        );
        tableTest(
                        $table.tablesorter(
                                { sortList: [ { 0: 'desc' }, { 1: 'desc' } ] }
                        );
-                       $table.find( '.headerSort:eq(0)' ).click();
+                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
 
                        // Pretend to click while pressing the multi-sort key
                        event = $.Event( 'click' );
                        $table.find( 'tr:eq(0) th:eq(0)' ).attr( 'colspan', '3' );
 
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).click();
+                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
                }
        );
        tableTest( 'Sorting with colspanned headers: sort spanned column twice',
                        $table.find( 'tr:eq(0) th:eq(0)' ).attr( 'colspan', '3' );
 
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).click();
-                       $table.find( '.headerSort:eq(0)' ).click();
+                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
+                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
                }
        );
        tableTest( 'Sorting with colspanned headers: subsequent column',
                        $table.find( 'tr:eq(0) th:eq(0)' ).attr( 'colspan', '3' );
 
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(1)' ).click();
+                       $table.find( '.headerSort:eq(1)' ).trigger( 'click' );
                }
        );
        tableTest( 'Sorting with colspanned headers: sort subsequent column twice',
                        $table.find( 'tr:eq(0) th:eq(0)' ).attr( 'colspan', '3' );
 
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(1)' ).click();
-                       $table.find( '.headerSort:eq(1)' ).click();
+                       $table.find( '.headerSort:eq(1)' ).trigger( 'click' );
+                       $table.find( '.headerSort:eq(1)' ).trigger( 'click' );
                }
        );
 
                $table.find( 'tr:eq(0) > th:eq(0)' ).addClass( 'unsortable' );
 
                $table.tablesorter();
-               $table.find( 'tr:eq(0) > th:eq(0)' ).click();
+               $table.find( 'tr:eq(0) > th:eq(0)' ).trigger( 'click' );
 
                assert.deepEqual(
                        tableExtract( $table ),
                );
 
                $cell = $table.find( 'tr:eq(0) > th:eq(0)' );
-               $table.find( 'tr:eq(0) > th:eq(1)' ).click();
+               $table.find( 'tr:eq(0) > th:eq(1)' ).trigger( 'click' );
 
                assert.strictEqual(
                        $cell.hasClass( 'headerSortUp' ) || $cell.hasClass( 'headerSortDown' ),
                        mw.config.set( 'wgPageContentLanguage', 'de' );
 
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).click();
+                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
                }
        );
 
                        mw.config.set( 'wgDefaultDateFormat', 'mdy' );
 
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).click();
+                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
                }
        );
 
                ipv4Sorted,
                function ( $table ) {
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).click();
+                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
                }
        );
        tableTest(
                reversed( ipv4Sorted ),
                function ( $table ) {
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).click().click();
+                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' ).trigger( 'click' );
                }
        );
 
                        } );
 
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).click();
+                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
                }
        );
 
                        } );
 
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).click();
+                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
                }
        );
 
                        $table.find( 'tr:eq(2) td:eq(1)' ).attr( 'rowspan', '3' );
 
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).click();
+                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
                }
        );
        tableTest(
                        $table.find( 'tr:eq(2) td:eq(0)' ).attr( 'rowspan', '3' );
 
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).click();
+                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
                }
        );
 
                        mw.config.set( 'wgDefaultDateFormat', 'mdy' );
 
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).click();
+                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
                }
        );
 
                currencySorted,
                function ( $table ) {
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).click();
+                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
                }
        );
 
                function ( $table ) {
                        $table.find( 'tr:last' ).addClass( 'sortbottom' );
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).click();
+                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
                }
        );
 
                                '</table>'
                );
                $table.tablesorter();
-               $table.find( '.headerSort:eq(0)' ).click();
+               $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
 
                assert.strictEqual(
                        $table.data( 'tablesorter' ).config.parsers[ 0 ].id,
                                '<tr><td data-sort-value="Cherry">Dolphin</td></tr>' +
                                '</tbody></table>'
                );
-               $table.tablesorter().find( '.headerSort:eq(0)' ).click();
+               $table.tablesorter().find( '.headerSort:eq(0)' ).trigger( 'click' );
 
                data = [];
                $table.find( 'tbody > tr' ).each( function ( i, tr ) {
                                '<tr><td><span data-sort-value="D">H</span></td></tr>' +
                                '</tbody></table>'
                );
-               $table.tablesorter().find( '.headerSort:eq(0)' ).click();
+               $table.tablesorter().find( '.headerSort:eq(0)' ).trigger( 'click' );
 
                data = [];
                $table.find( 'tbody > tr' ).each( function ( i, tr ) {
                // initialize table sorter and sort once
                $table
                        .tablesorter()
-                       .find( '.headerSort:eq(0)' ).click();
+                       .find( '.headerSort:eq(0)' ).trigger( 'click' );
 
                // Change the sortValue data properties (T40152)
                // - change data
                $table.find( 'td:contains(G)' ).removeData( 'sortValue' );
 
                // Now sort again (twice, so it is back at Ascending)
-               $table.find( '.headerSort:eq(0)' ).click();
-               $table.find( '.headerSort:eq(0)' ).click();
+               $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
+               $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
 
                data = [];
                $table.find( 'tbody > tr' ).each( function ( i, tr ) {
                [ 'Numbers' ], numbers, numbersAsc,
                function ( $table ) {
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).click();
+                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
                }
        );
 
                [ 'Numbers' ], numbers, reversed( numbersAsc ),
                function ( $table ) {
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).click().click();
+                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' ).trigger( 'click' );
                }
        );
        // TODO add numbers sorting tests for T10115 with a different language
                        mw.config.set( 'wgDefaultDateFormat', 'mdy' );
 
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).click();
+                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
                }
        );
 
                        mw.config.set( 'wgDefaultDateFormat', 'dmy' );
 
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).click();
+                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
                }
        );
 
                        mw.config.set( 'wgDefaultDateFormat', 'dmy' );
 
                        $table.tablesorter();
-                       $table.find( '.headerSort:eq(0)' ).click();
+                       $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
                }
        );
 
                                '<tr><td>1</td></tr>' +
                                '</table>'
                );
-               $table.tablesorter().find( '.headerSort:eq(0)' ).click();
+               $table.tablesorter().find( '.headerSort:eq(0)' ).trigger( 'click' );
 
                assert.strictEqual(
                        $table.find( 'td' ).first().text(),
                                '<tr><td><img alt="A" />C</tr>' +
                                '</table>'
                );
-               $table.tablesorter().find( '.headerSort:eq(0)' ).click();
+               $table.tablesorter().find( '.headerSort:eq(0)' ).trigger( 'click' );
 
                assert.strictEqual(
                        $table.find( 'td' ).text(),
                                '<tr><td>4</td></tr>' +
                                '</table>'
                );
-               $table.tablesorter().find( '.headerSort:eq(0)' ).click();
+               $table.tablesorter().find( '.headerSort:eq(0)' ).trigger( 'click' );
 
                assert.strictEqual(
                        $table.find( 'td' ).text(),
                                '</table>'
                );
                $table.tablesorter();
-               $table.find( '.headerSort:eq(0)' ).click();
+               $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
                // now the first row have 2 columns
-               $table.find( '.headerSort:eq(1)' ).click();
+               $table.find( '.headerSort:eq(1)' ).trigger( 'click' );
 
                parsers = $table.data( 'tablesorter' ).config.parsers;
 
                                '</table>'
                );
                $table.tablesorter();
-               $table.find( '.headerSort:eq(0)' ).click();
+               $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
 
                assert.deepEqual(
                        tableExtract( $table ),
                                '</table>'
                );
                $table.tablesorter();
-               $table.find( '.headerSort:eq(0)' ).click();
+               $table.find( '.headerSort:eq(0)' ).trigger( 'click' );
 
                assert.deepEqual(
                        tableExtract( $table ),
index 54fdffc..458df92 100644 (file)
@@ -43,7 +43,7 @@
                $options = $( '#namespace' ).find( 'option' );
                $options.eq( 0 ).removeProp( 'selected' );
                $options.eq( 1 ).prop( 'selected', true );
-               $( '#namespace' ).change();
+               $( '#namespace' ).trigger( 'change' );
 
                // ... and checkboxes should be enabled again
                assert.strictEqual( $( '#nsinvert' ).prop( 'disabled' ), false );
@@ -52,7 +52,7 @@
                // select first option ( 'all' namespace)...
                $options.eq( 1 ).removeProp( 'selected' );
                $options.eq( 0 ).prop( 'selected', true );
-               $( '#namespace' ).change();
+               $( '#namespace' ).trigger( 'change' );
 
                // ... and checkboxes should now be disabled
                assert.strictEqual( $( '#nsinvert' ).prop( 'disabled' ), true );
index b9a64b5..74fd743 100644 (file)
 
                assert.strictEqual( $tocList.is( ':hidden' ), false, 'The table of contents is now visible' );
 
-               $toggleLink.click();
+               $toggleLink.trigger( 'click' );
                return $tocList.promise().then( function () {
                        assert.strictEqual( $tocList.is( ':hidden' ), true, 'The table of contents is now hidden' );
 
-                       $toggleLink.click();
+                       $toggleLink.trigger( 'click' );
                        return $tocList.promise();
                } );
        } );