source 'https://rubygems.org'
-gem 'mediawiki_selenium', '~> 1.7', '>= 1.7.1'
+gem 'mediawiki_selenium', '~> 1.7', '>= 1.7.2'
gem 'rake', '~> 11.1', '>= 11.1.1'
gem 'rubocop', '~> 0.32.1', require: false
faker (>= 1.1.2)
yml_reader (>= 0.6)
diff-lcs (1.2.5)
- domain_name (0.5.20160310)
+ domain_name (0.5.20160615)
unf (>= 0.0.5, < 1.0.0)
- faker (1.6.3)
+ faker (1.6.6)
i18n (~> 0.5)
faraday (0.9.2)
multipart-post (>= 1.2, < 3)
faraday-cookie_jar (0.0.6)
faraday (>= 0.7.4)
http-cookie (~> 1.0.0)
- ffi (1.9.10)
+ faraday_middleware (0.10.0)
+ faraday (>= 0.7.4, < 0.10)
+ ffi (1.9.14)
gherkin (2.12.2)
multi_json (~> 1.3)
headless (2.2.3)
domain_name (~> 0.5)
i18n (0.7.0)
json (1.8.3)
- mediawiki_api (0.6.0)
+ mediawiki_api (0.7.0)
faraday (~> 0.9, >= 0.9.0)
faraday-cookie_jar (~> 0.0, >= 0.0.6)
- mediawiki_selenium (1.7.1)
+ faraday_middleware (~> 0.10, >= 0.10.0)
+ mediawiki_selenium (1.7.2)
cucumber (~> 1.3, >= 1.3.20)
headless (~> 2.0, >= 2.1.0)
json (~> 1.8, >= 1.8.1)
- mediawiki_api (~> 0.6, >= 0.6.0)
+ mediawiki_api (~> 0.7, >= 0.7.0)
page-object (~> 1.0)
rest-client (~> 1.6, >= 1.6.7)
rspec-core (~> 2.14, >= 2.14.4)
multi_test (0.1.2)
multipart-post (2.0.0)
netrc (0.11.0)
- page-object (1.1.1)
+ page-object (1.2.0)
page_navigation (>= 0.9)
selenium-webdriver (>= 2.44.0)
watir-webdriver (>= 0.6.11)
ruby-progressbar (~> 1.4)
ruby-progressbar (1.7.5)
rubyzip (1.2.0)
- selenium-webdriver (2.53.1)
+ selenium-webdriver (2.53.4)
childprocess (~> 0.5)
rubyzip (~> 1.0)
websocket (~> 1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.2)
- watir-webdriver (0.9.1)
+ watir-webdriver (0.9.3)
selenium-webdriver (>= 2.46.2)
websocket (1.2.3)
yml_reader (0.7)
ruby
DEPENDENCIES
- mediawiki_selenium (~> 1.7, >= 1.7.1)
+ mediawiki_selenium (~> 1.7, >= 1.7.2)
rake (~> 11.1, >= 11.1.1)
rubocop (~> 0.32.1)
-
-BUNDLED WITH
- 1.10.6
* DifferenceEngine::generateDiffBody() was removed (deprecated since 1.21).
* UploadBase::stashFileGetKey() and UploadBase::stashSession() were deprecated.
Use ...->stashFile()->getFileKey() instead.
+* "Public domain" was removed as a wiki license option from the installer, in
+ favour of CC-0.
+* AuthenticationRequest::$required is now changed from REQUIRED to PRIMARY_REQUIRED
+ on requests needed by primary providers even if all primaries need them.
+ Primary providers are discouraged from returning multiple REQUIRED requests.
== Compatibility ==
"ext-xml": "*",
"liuggio/statsd-php-client": "1.0.18",
"mediawiki/at-ease": "1.1.0",
- "oojs/oojs-ui": "0.17.7",
+ "oojs/oojs-ui": "0.17.8",
"oyejorge/less.php": "1.7.0.10",
"php": ">=5.5.9",
"psr/log": "1.0.0",
case self::AS_CANNOT_USE_CUSTOM_MODEL:
case self::AS_PARSE_ERROR:
- $wgOut->addWikiText( '<div class="error">' . $status->getWikiText() . '</div>' );
+ $wgOut->addWikiText( '<div class="error">' . "\n" . $status->getWikiText() . '</div>' );
return true;
case self::AS_SUCCESS_NEW_ARTICLE:
// is if an extension hook aborted from inside ArticleSave.
// Render the status object into $this->hookError
// FIXME this sucks, we should just use the Status object throughout
- $this->hookError = '<div class="error">' . $status->getWikiText() .
+ $this->hookError = '<div class="error">' ."\n" . $status->getWikiText() .
'</div>';
return true;
}
* 3) Adds a "delimiter" element to the array, either '://', ':' or '//' (see (2)).
*
* @param string $url A URL to parse
- * @return string[] Bits of the URL in an associative array, per PHP docs
+ * @return string[]|bool Bits of the URL in an associative array, per PHP docs, false on failure
*/
function wfParseUrl( $url ) {
global $wgUrlProtocols; // Allow all protocols defined in DefaultSettings/LocalSettings.php
$pageid = $this->oldTitle->getArticleID( Title::GAID_FOR_UPDATE );
$protected = $this->oldTitle->isProtected();
- // Do the actual move
+ // Do the actual move; if this fails, it will throw an MWException(!)
$nullRevision = $this->moveToInternal( $user, $this->newTitle, $reason, $createRedirect );
// Refresh the sortkey for this row. Be careful to avoid resetting
*
* @fixme This was basically directly moved from Title, it should be split into smaller functions
* @param User $user the User doing the move
- * @param Title $nt The page to move to, which should be a redirect or nonexistent
+ * @param Title $nt The page to move to, which should be a redirect or non-existent
* @param string $reason The reason for the move
* @param bool $createRedirect Whether to leave a redirect at the old title. Does not check
* if the user has the suppressredirect right
$logType = 'move';
}
+ if ( $moveOverRedirect ) {
+ $overwriteMessage = wfMessage(
+ 'delete_and_move_reason',
+ $this->oldTitle->getPrefixedText()
+ )->text();
+ $newpage = WikiPage::factory( $nt );
+ $errs = [];
+ $status = $newpage->doDeleteArticleReal(
+ $overwriteMessage,
+ /* $suppress */ false,
+ $nt->getArticleId(),
+ /* $commit */ false,
+ $errs,
+ $user
+ );
+
+ if ( !$status->isGood() ) {
+ throw new MWException( 'Failed to delete page-move revision: ' . $status );
+ }
+
+ $nt->resetArticleID( false );
+ }
+
if ( $createRedirect ) {
if ( $this->oldTitle->getNamespace() == NS_CATEGORY
&& !wfMessage( 'category-move-redirect-override' )->inContentLanguage()->isDisabled()
$newpage = WikiPage::factory( $nt );
- if ( $moveOverRedirect ) {
- $newid = $nt->getArticleID();
- $newcontent = $newpage->getContent();
-
- # Delete the old redirect. We don't save it to history since
- # by definition if we've got here it's rather uninteresting.
- # We have to remove it so that the next step doesn't trigger
- # a conflict on the unique namespace+title index...
- $dbw->delete( 'page', [ 'page_id' => $newid ], __METHOD__ );
-
- $newpage->doDeleteUpdates( $newid, $newcontent );
- }
-
# Save a null revision in the page's history notifying of the move
$nullRevision = Revision::newNullRevision( $dbw, $oldid, $comment, true, $user );
if ( !is_object( $nullRevision ) ) {
);
}
- if ( !$moveOverRedirect ) {
- WikiPage::onArticleCreate( $nt );
- }
+ WikiPage::onArticleCreate( $nt );
# Recreate the redirect, this time in the other direction.
if ( $redirectContent ) {
'user.options',
'user.tokens',
] );
- $rlClient = new ResourceLoaderClientHtml( $context );
+ $rlClient = new ResourceLoaderClientHtml( $context, $this->getTarget() );
$rlClient->setConfig( $this->getJSVars() );
$rlClient->setModules( $this->getModules( /*filter*/ true ) );
$rlClient->setModuleStyles( $this->getModuleStyles( /*filter*/ true ) );
* @return string|WrappedStringList HTML
*/
public function makeResourceLoaderLink( $modules, $only, array $extraQuery = [] ) {
+ // Apply 'target' and 'origin' filters
+ $modules = $this->filterModules( (array)$modules, null, $only );
+
return ResourceLoaderClientHtml::makeLoad(
$this->getRlClientContext(),
- (array)$modules,
+ $modules,
$only,
$extraQuery
);
'oojs-ui.styles.textures',
'mediawiki.widgets.styles',
] );
- // Used by 'skipFunction' of the four 'oojs-ui.styles.*' modules. Please don't treat this as a
- // public API or you'll be severely disappointed when T87871 is fixed and it disappears.
- $this->addMeta( 'X-OOUI-PHP', '1' );
}
/**
/** @var null|string */
protected $class;
+ /** @var null|callable */
+ protected $factory;
+
/** @var array */
protected $params;
* Constructor.
*
* @param string $global Name of the global variable.
- * @param string $class Name of the class of the real object.
+ * @param string|callable $class Name of the class of the real object
+ * or a factory function to call
* @param array $params Parameters to pass to constructor of the real object.
*/
public function __construct( $global = null, $class = null, $params = [] ) {
$this->global = $global;
- $this->class = $class;
+ if ( is_callable( $class ) ) {
+ $this->factory = $class;
+ } else {
+ $this->class = $class;
+ }
$this->params = $params;
}
* @return object
*/
public function _newObject() {
- return ObjectFactory::getObjectFromSpec( [
- 'class' => $this->class,
+ $params = $this->factory
+ ? [ 'factory' => $this->factory ]
+ : [ 'class' => $this->class ];
+ return ObjectFactory::getObjectFromSpec( $params + [
'args' => $this->params,
'closure_expansion' => false,
] );
global $wgUpdateRowsPerQuery;
$dbw = $this->getConnection( DB_MASTER );
+ $factory = wfGetLBFactory();
+ $ticket = $factory->getEmptyTransactionTicket( __METHOD__ );
$watchersChunks = array_chunk( $watchers, $wgUpdateRowsPerQuery );
foreach ( $watchersChunks as $watchersChunk ) {
], $fname
);
if ( count( $watchersChunks ) > 1 ) {
- $dbw->commit( __METHOD__, 'flush' );
- wfGetLBFactory()->waitForReplication( [ 'wiki' => $dbw->getWikiID() ] );
+ $factory->commitAndWaitForReplication(
+ __METHOD__, $ticket, [ 'wiki' => $dbw->getWikiID() ]
+ );
}
}
$this->uncacheLinkTarget( $target );
$this->formatMessage( $ret, 'label', $field['label'] );
$this->formatMessage( $ret, 'help', $field['help'] );
$ret['optional'] = !empty( $field['optional'] );
+ $ret['sensitive'] = !empty( $field['sensitive'] );
$retFields[$name] = $ret;
}
// Build map of extension directories to extension info
if ( self::$extensionInfo === null ) {
+ $extDir = $this->getConfig()->get( 'ExtensionDirectory' );
self::$extensionInfo = [
realpath( __DIR__ ) ?: __DIR__ => [
'path' => $IP,
'license-name' => 'GPL-2.0+',
],
realpath( "$IP/extensions" ) ?: "$IP/extensions" => null,
+ realpath( $extDir ) ?: $extDir => null,
];
$keep = [
'path' => null,
$authRes = 'Failed';
$message = $res->message;
\MediaWiki\Logger\LoggerFactory::getInstance( 'authentication' )
- ->info( __METHOD__ . ': Authentication failed: ' . $message->plain() );
+ ->info( __METHOD__ . ': Authentication failed: '
+ . $message->inLanguage( 'en' )->plain() );
break;
default:
+ \MediaWiki\Logger\LoggerFactory::getInstance( 'authentication' )
+ ->info( __METHOD__ . ': Authentication failed due to unsupported response type: '
+ . $res->status, $this->getAuthenticationResponseLogData( $res ) );
$authRes = 'Aborted';
break;
}
public function getHelpUrls() {
return 'https://www.mediawiki.org/wiki/API:Login';
}
+
+ /**
+ * Turns an AuthenticationResponse into a hash suitable for passing to Logger
+ * @param AuthenticationResponse $response
+ * @return array
+ */
+ protected function getAuthenticationResponseLogData( AuthenticationResponse $response ) {
+ $ret = [
+ 'status' => $response->status,
+ ];
+ if ( $response->message ) {
+ $ret['message'] = $response->message->inLanguage( 'en' )->plain();
+ };
+ $reqs = [
+ 'neededRequests' => $response->neededRequests,
+ 'createRequest' => $response->createRequest,
+ 'linkRequest' => $response->linkRequest,
+ ];
+ foreach ( $reqs as $k => $v ) {
+ if ( $v ) {
+ $v = is_array( $v ) ? $v : [ $v ];
+ $reqClasses = array_unique( array_map( 'get_class', $v ) );
+ sort( $reqClasses );
+ $ret[$k] = implode( ', ', $reqClasses );
+ }
+ }
+ return $ret;
+ }
}
* @defgroup API API
*/
+use MediaWiki\Logger\LoggerFactory;
+
/**
* This is the main API class, used for both external and internal processing.
* When executed, it will create the requested formatter object,
$config->get( 'CrossSiteAJAXdomainExceptions' )
)
) ) {
- MediaWiki\Logger\LoggerFactory::getInstance( 'cors' )->warning(
+ LoggerFactory::getInstance( 'cors' )->warning(
'Non-whitelisted CORS request with session cookies', [
'origin' => $originHeader,
'cookies' => $sessionCookies,
protected function setRequestExpectations( ApiBase $module ) {
$limits = $this->getConfig()->get( 'TrxProfilerLimits' );
$trxProfiler = Profiler::instance()->getTransactionProfiler();
+ $trxProfiler->setLogger( LoggerFactory::getInstance( 'DBPerformance' ) );
if ( $this->getRequest()->hasSafeMethod() ) {
$trxProfiler->setExpectations( $limits['GET'], __METHOD__ );
} elseif ( $this->getRequest()->wasPosted() && !$module->isWriteMode() ) {
'code' => 'lh',
'prefix' => 'pl',
'linktable' => 'pagelinks',
+ 'indexes' => [ 'pl_namespace', 'pl_backlinks_namespace' ],
'from_namespace' => true,
'showredirects' => true,
],
'code' => 'ti',
'prefix' => 'tl',
'linktable' => 'templatelinks',
+ 'indexes' => [ 'tl_namespace', 'tl_backlinks_namespace' ],
'from_namespace' => true,
'showredirects' => true,
],
'code' => 'fu',
'prefix' => 'il',
'linktable' => 'imagelinks',
+ 'indexes' => [ 'il_to', 'il_backlinks_namespace' ],
'from_namespace' => true,
'to_namespace' => NS_FILE,
'exampletitle' => 'File:Example.jpg',
// Override any ORDER BY from above with what we calculated earlier.
$this->addOption( 'ORDER BY', array_keys( $sortby ) );
+ // MySQL's optimizer chokes if we have too many values in "$bl_title IN
+ // (...)" and chooses the wrong index, so specify the correct index to
+ // use for the query. See T139056 for details.
+ if ( !empty( $settings['indexes'] ) ) {
+ list( $idxNoFromNS, $idxWithFromNS ) = $settings['indexes'];
+ if ( $params['namespace'] !== null && !empty( $settings['from_namespace'] ) ) {
+ $this->addOption( 'USE INDEX', [ $settings['linktable'] => $idxWithFromNS ] );
+ } else {
+ $this->addOption( 'USE INDEX', [ $settings['linktable'] => $idxNoFromNS ] );
+ }
+ }
+
$this->addOption( 'LIMIT', $params['limit'] + 1 );
$res = $this->select( __METHOD__ );
'offset' => $this->mUpload->getOffset(),
];
- $this->dieUsage( $status->getWikiText( false, false, 'en' ), 'stashfailed', 0, $extradata );
+ $this->dieStatusWithCode( $status, 'stashfailed', $extradata );
}
}
$filekey,
[ 'result' => 'Failure', 'stage' => 'assembling', 'status' => $status ]
);
- $this->dieUsage( $status->getWikiText( false, false, 'en' ), 'stashfailed' );
+ $this->dieStatusWithCode( $status, 'stashfailed' );
}
// The fully concatenated file has a new filekey. So remove
* @return string|null File key
*/
private function performStash( $failureMode, &$data = null ) {
+ $isPartial = (bool)$this->mParams['chunk'];
try {
- $status = $this->mUpload->tryStashFile( $this->getUser() );
+ $status = $this->mUpload->tryStashFile( $this->getUser(), $isPartial );
if ( $status->isGood() && !$status->getValue() ) {
// Not actually a 'good' status...
$this->dieUsage( $parsed['info'], $parsed['code'], 0, $data );
}
+ /**
+ * Like dieStatus(), but always uses $overrideCode for the error code, unless the code comes from
+ * IApiMessage.
+ *
+ * @param Status $status
+ * @param string $overrideCode Error code to use if there isn't one from IApiMessage
+ * @param array|null $moreExtraData
+ * @throws UsageException
+ */
+ public function dieStatusWithCode( $status, $overrideCode, $moreExtraData = null ) {
+ $extraData = null;
+ list( $code, $msg ) = $this->getErrorFromStatus( $status, $extraData );
+ $errors = $status->getErrorsByType( 'error' ) ?: $status->getErrorsByType( 'warning' );
+ if ( !( $errors[0]['message'] instanceof IApiMessage ) ) {
+ $code = $overrideCode;
+ }
+ if ( $moreExtraData ) {
+ $extraData += $moreExtraData;
+ }
+ $this->dieUsage( $msg, $code, 0, $extraData );
+ }
+
/**
* Select an upload module and set it to mUpload. Dies on failure. If the
* request was a status request and not a true upload, returns false;
if ( !$progress ) {
$this->dieUsage( 'No result in status data', 'missingresult' );
} elseif ( !$progress['status']->isGood() ) {
- $this->dieUsage( $progress['status']->getWikiText( false, false, 'en' ), 'stashfailed' );
+ $this->dieStatusWithCode( $progress['status'], 'stashfailed' );
}
if ( isset( $progress['status']->value['verification'] ) ) {
$this->checkVerification( $progress['status']->value['verification'] );
if ( $this->mParams['chunk'] ) {
// Chunk upload
- $this->mUpload = new UploadFromChunks();
+ $this->mUpload = new UploadFromChunks( $this->getUser() );
if ( isset( $this->mParams['filekey'] ) ) {
if ( $this->mParams['offset'] === 0 ) {
$this->dieUsage( 'Cannot supply a filekey when offset is 0', 'badparams' );
"Lbayle",
"Verdy p",
"Yasten",
- "Trial"
+ "Trial",
+ "Pols12"
]
},
"apihelp-main-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:API:Main_page|Documentation]]\n* [[mw:API:FAQ|FAQ]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Liste de diffusion]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Annonces de l’API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Bogues et demandes]\n</div>\n<strong>État :</strong> Toutes les fonctionnalités affichées sur cette page devraient fonctionner, mais l’API est encore en cours de développement et peut changer à tout moment. Inscrivez-vous à [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ la liste de diffusion mediawiki-api-announce] pour être informé des mises à jour.\n\n<strong>Requêtes erronées :</strong> Si des requêtes erronées sont envoyées à l’API, un en-tête HTTP sera renvoyé avec la clé « MediaWiki-API-Error ». La valeur de cet en-tête et le code d’erreur renvoyé prendront la même valeur. Pour plus d’information, voyez [[mw:API:Errors_and_warnings|API: Errors and warnings]].\n\n<strong>Test :</strong> Pour faciliter le test des requêtes de l’API, voyez [[Special:ApiSandbox]].",
"apihelp-stashedit-param-section": "Numéro de section. <kbd>0</kbd> pour la section du haut, <kbd>new</kbd> pour une nouvelle section.",
"apihelp-stashedit-param-sectiontitle": "Le titre pour une nouvelle section.",
"apihelp-stashedit-param-text": "Contenu de la page.",
- "apihelp-stashedit-param-stashedtexthash": "A substituer par le hachage du contenu de la page venant d’une réserve antérieure.",
+ "apihelp-stashedit-param-stashedtexthash": "Empreinte du contenu de la page venant d’une réserve préalable à utiliser à la place.",
"apihelp-stashedit-param-contentmodel": "Modèle de contenu du nouveau contenu.",
"apihelp-stashedit-param-contentformat": "Format de sérialisation de contenu utilisé pour le texte saisi.",
"apihelp-stashedit-param-baserevid": "ID de révision de la révision de base.",
"apihelp-query+search-paramvalue-prop-snippet": "Adds a parsed snippet of the page.",
"apihelp-query+search-paramvalue-prop-titlesnippet": "Adds a parsed snippet of the page title.",
"apihelp-query+search-paramvalue-prop-redirectsnippet": "添加被解析的重定向标题的片段。",
- "apihelp-query+search-paramvalue-prop-redirecttitle": "Adds the title of the matching redirect.",
+ "apihelp-query+search-paramvalue-prop-redirecttitle": "添加匹配的重定向的标题。",
"apihelp-query+search-paramvalue-prop-sectionsnippet": "Adds a parsed snippet of the matching section title.",
"apihelp-query+search-paramvalue-prop-sectiontitle": "Adds the title of the matching section.",
"apihelp-query+search-paramvalue-prop-categorysnippet": "Adds a parsed snippet of the matching category.",
"apihelp-query+search-param-enablerewrites": "启用内部查询重写。一些搜索后端可以重写查询到它认为会给出更好结果的地方,例如纠正拼写错误。",
"apihelp-query+search-example-simple": "搜索<kbd>meaning</kbd>。",
"apihelp-query+search-example-text": "搜索文本<kbd>meaning</kbd>。",
- "apihelp-query+search-example-generator": "è\8e·å¾\97有关搜索<kbd>meaning</kbd>返回页面的页面信息。",
+ "apihelp-query+search-example-generator": "è\8e·å\8f\96有关搜索<kbd>meaning</kbd>返回页面的页面信息。",
"apihelp-query+siteinfo-description": "返回有关网站的一般信息。",
"apihelp-query+siteinfo-param-prop": "要获取的信息:",
"apihelp-query+siteinfo-paramvalue-prop-general": "全部系统信息。",
$logEntry->setParameters( [
'4::userid' => $user->getId(),
] );
- $logid = $logEntry->insert();
+ $logEntry->insert();
}
- // Commit database changes, so even if something else later blows up
- // the newly-created user doesn't get lost.
- wfGetLBFactory()->commitMasterChanges( __METHOD__ );
-
$trxProfiler->setSilenced( false );
if ( $login ) {
// Query them and merge results
$reqs = [];
- $allPrimaryRequired = null;
foreach ( $providers as $provider ) {
$isPrimary = $provider instanceof PrimaryAuthenticationProvider;
- $thisRequired = [];
foreach ( $provider->getAuthenticationRequests( $providerAction, $options ) as $req ) {
$id = $req->getUniqueId();
- // If it's from a Primary, mark it as "primary-required" but
- // track it for later.
+ // If a required request if from a Primary, mark it as "primary-required" instead
if ( $isPrimary ) {
if ( $req->required ) {
- $thisRequired[$id] = true;
$req->required = AuthenticationRequest::PRIMARY_REQUIRED;
}
}
- if ( !isset( $reqs[$id] ) || $req->required === AuthenticationRequest::REQUIRED ) {
+ if (
+ !isset( $reqs[$id] )
+ || $req->required === AuthenticationRequest::REQUIRED
+ || $reqs[$id] === AuthenticationRequest::OPTIONAL
+ ) {
$reqs[$id] = $req;
}
}
-
- // Track which requests are required by all primaries
- if ( $isPrimary ) {
- $allPrimaryRequired = $allPrimaryRequired === null
- ? $thisRequired
- : array_intersect_key( $allPrimaryRequired, $thisRequired );
- }
- }
- // Any requests that were required by all primaries are required.
- foreach ( (array)$allPrimaryRequired as $id => $dummy ) {
- $reqs[$id]->required = AuthenticationRequest::REQUIRED;
}
// AuthManager has its own req for some actions
const REQUIRED = 1;
/** Indicates that the request is required by a primary authentication
- * provdier, but other primary authentication providers do not require it. */
+ * provdier. Since the user can choose which primary to authenticate with,
+ * the request might or might not end up being actually required. */
const PRIMARY_REQUIRED = 2;
/** @var string|null The AuthManager::ACTION_* constant this request was
* - label: (Message) Text suitable for a label in an HTML form
* - help: (Message) Text suitable as a description of what the field is
* - optional: (bool) If set and truthy, the field may be left empty
+ * - sensitive: (bool) If set and truthy, the field is considered sensitive. Code using the
+ * request should avoid exposing the value of the field.
*
* @return array As above
*/
$options['optional'] = !empty( $options['optional'] );
}
+ $options['sensitive'] = !empty( $options['sensitive'] );
+
if ( !array_key_exists( $name, $merged ) ) {
$merged[$name] = $options;
} elseif ( $merged[$name]['type'] !== $options['type'] ) {
}
$merged[$name]['optional'] = $merged[$name]['optional'] && $options['optional'];
+ $merged[$name]['sensitive'] = $merged[$name]['sensitive'] || $options['sensitive'];
// No way to merge 'value', 'image', 'help', or 'label', so just use
// the value from the first request.
'type' => 'password',
'label' => wfMessage( $passwordLabel ),
'help' => wfMessage( 'authmanager-password-help' ),
+ 'sensitive' => true,
],
];
'type' => 'password',
'label' => wfMessage( $retypeLabel ),
'help' => wfMessage( 'authmanager-retype-help' ),
+ 'sensitive' => true,
];
}
/** Provider cannot create or link to accounts */
const TYPE_NONE = 'none';
+ /**
+ * {@inheritdoc}
+ *
+ * Of the requests returned by this method, exactly one should have
+ * {@link AuthenticationRequest::$required} set to REQUIRED.
+ */
+ public function getAuthenticationRequests( $action, array $options );
+
/**
* Start an authentication flow
*
);
if ( $sendMail ) {
- $this->sendPasswordResetEmail( $req );
+ // Send email after DB commit
+ $dbw->onTransactionIdle( function () use ( $req ) {
+ /** @var TemporaryPasswordAuthenticationRequest $req */
+ $this->sendPasswordResetEmail( $req );
+ } );
}
}
$this->providerChangeAuthenticationData( $req );
if ( $mailpassword ) {
- $this->sendNewAccountEmail( $user, $creator, $req->password );
+ // Send email after DB commit
+ wfGetDB( DB_MASTER )->onTransactionIdle( function () use ( $user, $creator, $req ) {
+ $this->sendNewAccountEmail( $user, $creator, $req->password );
+ } );
}
return $mailpassword ? 'byemail' : null;
*
* @param string $event Event name
* @param array $args Parameters passed to hook functions
- * @param bool $warn Whether to log a warning.
- * Default to self::$enableDeprecationWarnings.
- * May be set to false for testing.
+ * @param string|null $deprecatedVersion Emit a deprecation notice
+ * when the hook is run for the provided version
*
* @return bool True if no handler aborted the hook
- *
- * @see ContentHandler::$enableDeprecationWarnings
*/
public static function runLegacyHooks( $event, $args = [],
- $warn = null
+ $deprecatedVersion = null
) {
- if ( $warn === null ) {
- $warn = self::$enableDeprecationWarnings;
- }
-
if ( !Hooks::isRegistered( $event ) ) {
return true; // nothing to do here
}
- if ( $warn ) {
- // Log information about which handlers are registered for the legacy hook,
- // so we can find and fix them.
-
- $handlers = Hooks::getHandlers( $event );
- $handlerInfo = [];
-
- MediaWiki\suppressWarnings();
-
- foreach ( $handlers as $handler ) {
- if ( is_array( $handler ) ) {
- if ( is_object( $handler[0] ) ) {
- $info = get_class( $handler[0] );
- } else {
- $info = $handler[0];
- }
-
- if ( isset( $handler[1] ) ) {
- $info .= '::' . $handler[1];
- }
- } elseif ( is_object( $handler ) ) {
- $info = get_class( $handler[0] );
- $info .= '::on' . $event;
- } else {
- $info = $handler;
- }
-
- $handlerInfo[] = $info;
- }
-
- MediaWiki\restoreWarnings();
-
- wfWarn( "Using obsolete hook $event via ContentHandler::runLegacyHooks()! Handlers: " .
- implode( ', ', $handlerInfo ), 2 );
- }
-
// convert Content objects to text
$contentObjects = [];
$contentTexts = [];
}
// call the hook functions
- $ok = Hooks::run( $event, $args );
+ $ok = Hooks::run( $event, $args, $deprecatedVersion );
// see if the hook changed the text
foreach ( $contentTexts as $k => $orig ) {
protected function getFileText( Title $title ) {
$file = wfLocalFile( $title );
if ( $file && $file->exists() ) {
- return $file->getHandler()->getEntireText( $file );
+ $handler = $file->getHandler();
+ if ( !$handler ) {
+ return null;
+ }
+ return $handler->getEntireText( $file );
}
return null;
return $this->__call( __FUNCTION__, func_get_args() );
}
- public function begin( $fname = __METHOD__ ) {
+ public function begin( $fname = __METHOD__, $mode = IDatabase::TRANSACTION_EXPLICIT ) {
return $this->__call( __FUNCTION__, func_get_args() );
}
" performing implicit commit before closing connection!" );
}
- $this->commit( __METHOD__, 'flush' );
+ $this->commit( __METHOD__, self::FLUSHING_INTERNAL );
}
$closed = $this->closeConnection();
if ( !$this->mTrxLevel && $this->getFlag( DBO_TRX )
&& $this->isTransactableQuery( $sql )
) {
- $this->begin( __METHOD__ . " ($fname)" );
+ $this->begin( __METHOD__ . " ($fname)", self::TRANSACTION_INTERNAL );
$this->mTrxAutomatic = true;
}
$useTrx = !$this->mTrxLevel;
if ( $useTrx ) {
- $this->begin( $fname );
+ $this->begin( $fname, self::TRANSACTION_INTERNAL );
}
try {
# Update any existing conflicting row(s)
throw $e;
}
if ( $useTrx ) {
- $this->commit( $fname );
+ $this->commit( $fname, self::TRANSACTION_INTERNAL );
}
return $ok;
$this->mTrxPreCommitCallbacks[] = [ $callback, wfGetCaller() ];
} else {
// If no transaction is active, then make one for this callback
- $this->begin( __METHOD__ );
+ $this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
try {
call_user_func( $callback );
$this->commit( __METHOD__ );
final public function startAtomic( $fname = __METHOD__ ) {
if ( !$this->mTrxLevel ) {
- $this->begin( $fname );
+ $this->begin( $fname, self::TRANSACTION_INTERNAL );
$this->mTrxAutomatic = true;
// If DBO_TRX is set, a series of startAtomic/endAtomic pairs will result
// in all changes being in one transaction to keep requests transactional.
}
if ( !$this->mTrxAtomicLevels && $this->mTrxAutomaticAtomic ) {
- $this->commit( $fname, 'flush' );
+ $this->commit( $fname, self::FLUSHING_INTERNAL );
}
}
final public function doAtomicSection( $fname, callable $callback ) {
$this->startAtomic( $fname );
try {
- call_user_func_array( $callback, [ $this, $fname ] );
+ $res = call_user_func_array( $callback, [ $this, $fname ] );
} catch ( Exception $e ) {
$this->rollback( $fname );
throw $e;
}
$this->endAtomic( $fname );
+
+ return $res;
}
- final public function begin( $fname = __METHOD__ ) {
- if ( $this->mTrxLevel ) { // implicit commit
+ final public function begin( $fname = __METHOD__, $mode = self::TRANSACTION_EXPLICIT ) {
+ // Protect against mismatched atomic section, transaction nesting, and snapshot loss
+ if ( $this->mTrxLevel ) {
if ( $this->mTrxAtomicLevels ) {
- // If the current transaction was an automatic atomic one, then we definitely have
- // a problem. Same if there is any unclosed atomic level.
$levels = implode( ', ', $this->mTrxAtomicLevels );
- throw new DBUnexpectedError(
- $this,
- "Got explicit BEGIN from $fname while atomic section(s) $levels are open."
- );
+ $msg = "$fname: Got explicit BEGIN while atomic section(s) $levels are open.";
+ throw new DBUnexpectedError( $this, $msg );
} elseif ( !$this->mTrxAutomatic ) {
- // We want to warn about inadvertently nested begin/commit pairs, but not about
- // auto-committing implicit transactions that were started by query() via DBO_TRX
- throw new DBUnexpectedError(
- $this,
- "$fname: Transaction already in progress (from {$this->mTrxFname}), " .
- " performing implicit commit!"
- );
- } elseif ( $this->mTrxDoneWrites ) {
- // The transaction was automatic and has done write operations
- throw new DBUnexpectedError(
- $this,
- "$fname: Automatic transaction with writes in progress" .
- " (from {$this->mTrxFname}), performing implicit commit!\n"
- );
- }
-
- $this->runOnTransactionPreCommitCallbacks();
- $writeTime = $this->pendingWriteQueryDuration();
- $this->doCommit( $fname );
- if ( $this->mTrxDoneWrites ) {
- $this->mDoneWrites = microtime( true );
- $this->getTransactionProfiler()->transactionWritingOut(
- $this->mServer, $this->mDBname, $this->mTrxShortId, $writeTime );
+ $msg = "$fname: Explicit transaction already active (from {$this->mTrxFname}).";
+ throw new DBUnexpectedError( $this, $msg );
+ } else {
+ // @TODO: make this an exception at some point
+ $msg = "$fname: Implicit transaction already active (from {$this->mTrxFname}).";
+ wfLogDBError( $msg );
+ return; // join the main transaction set
}
-
- $this->runOnTransactionIdleCallbacks( self::TRIGGER_COMMIT );
+ } elseif ( $this->getFlag( DBO_TRX ) && $mode !== self::TRANSACTION_INTERNAL ) {
+ // @TODO: make this an exception at some point
+ wfLogDBError( "$fname: Implicit transaction expected (DBO_TRX set)." );
+ return; // let any writes be in the main transaction
}
// Avoid fatals if close() was called
$levels = implode( ', ', $this->mTrxAtomicLevels );
throw new DBUnexpectedError(
$this,
- "Got COMMIT while atomic sections $levels are still open"
+ "$fname: Got COMMIT while atomic sections $levels are still open."
);
}
- if ( $flush === 'flush' ) {
+ if ( $flush === self::FLUSHING_INTERNAL || $flush === self::FLUSHING_ALL_PEERS ) {
if ( !$this->mTrxLevel ) {
return; // nothing to do
} elseif ( !$this->mTrxAutomatic ) {
throw new DBUnexpectedError(
$this,
- "$fname: Flushing an explicit transaction, getting out of sync!"
+ "$fname: Flushing an explicit transaction, getting out of sync."
);
}
} else {
if ( !$this->mTrxLevel ) {
- wfWarn( "$fname: No transaction to commit, something got out of sync!" );
+ wfWarn( "$fname: No transaction to commit, something got out of sync." );
return; // nothing to do
} elseif ( $this->mTrxAutomatic ) {
- throw new DBUnexpectedError(
- $this,
- "$fname: Explicit commit of implicit transaction."
- );
+ // @TODO: make this an exception at some point
+ wfLogDBError( "$fname: Explicit commit of implicit transaction." );
+ return; // wait for the main transaction set commit round
}
}
}
final public function rollback( $fname = __METHOD__, $flush = '' ) {
- if ( $flush !== 'flush' ) {
+ if ( $flush === self::FLUSHING_INTERNAL || $flush === self::FLUSHING_ALL_PEERS ) {
if ( !$this->mTrxLevel ) {
- wfWarn( "$fname: No transaction to rollback, something got out of sync!" );
return; // nothing to do
}
} else {
if ( !$this->mTrxLevel ) {
+ wfWarn( "$fname: No transaction to rollback, something got out of sync." );
return; // nothing to do
+ } elseif ( $this->getFlag( DBO_TRX ) ) {
+ throw new DBUnexpectedError(
+ $this,
+ "$fname: Expected mass rollback of all peer databases (DBO_TRX set)."
+ );
}
}
}
public function getScopedLockAndFlush( $lockKey, $fname, $timeout ) {
+ if ( $this->writesOrCallbacksPending() ) {
+ // This only flushes transactions to clear snapshots, not to write data
+ throw new DBUnexpectedError(
+ $this,
+ "$fname: Cannot COMMIT to clear snapshot because writes are pending."
+ );
+ }
+
if ( !$this->lock( $lockKey, $fname, $timeout ) ) {
return null;
}
$unlocker = new ScopedCallback( function () use ( $lockKey, $fname ) {
- $this->commit( __METHOD__, 'flush' );
- $this->unlock( $lockKey, $fname );
+ if ( $this->trxLevel() ) {
+ // There is a good chance an exception was thrown, causing any early return
+ // from the caller. Let any error handler get a chance to issue rollback().
+ // If there isn't one, let the error bubble up and trigger server-side rollback.
+ $this->onTransactionResolution( function () use ( $lockKey, $fname ) {
+ $this->unlock( $lockKey, $fname );
+ } );
+ } else {
+ $this->unlock( $lockKey, $fname );
+ }
} );
- $this->commit( __METHOD__, 'flush' );
+ $this->commit( __METHOD__, self::FLUSHING_INTERNAL );
return $unlocker;
}
return 0; // already reached this point for sure
}
- // Commit any open transactions
- $this->commit( __METHOD__, 'flush' );
-
// Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
if ( $this->useGTIDs && $pos->gtids ) {
// Wait on the GTID set (MariaDB only)
$this->didbegin = false;
/* If we are not in a transaction, we need to be for savepoint trickery */
if ( !$dbw->trxLevel() ) {
- $dbw->begin( "FOR SAVEPOINT" );
+ $dbw->begin( "FOR SAVEPOINT", DatabasePostgres::TRANSACTION_INTERNAL );
$this->didbegin = true;
}
}
* @param string $desiredSchema
*/
function determineCoreSchema( $desiredSchema ) {
- $this->begin( __METHOD__ );
+ $this->begin( __METHOD__, self::TRANSACTION_INTERNAL );
if ( $this->schemaExists( $desiredSchema ) ) {
if ( in_array( $desiredSchema, $this->getSchemas() ) ) {
$this->mCoreSchema = $desiredSchema;
* @ingroup Database
*/
interface IDatabase {
- /* Constants to onTransactionResolution() callbacks */
+ /** @var int Callback triggered immediately due to no active transaction */
const TRIGGER_IDLE = 1;
+ /** @var int Callback triggered by commit */
const TRIGGER_COMMIT = 2;
+ /** @var int Callback triggered by rollback */
const TRIGGER_ROLLBACK = 3;
+ /** @var string Transaction is requested by regular caller outside of the DB layer */
+ const TRANSACTION_EXPLICIT = '';
+ /** @var string Transaction is requested interally via DBO_TRX/startAtomic() */
+ const TRANSACTION_INTERNAL = 'implicit';
+
+ /** @var string Transaction operation comes from service managing all DBs */
+ const FLUSHING_ALL_PEERS = 'flush';
+ /** @var string Transaction operation comes from the database class internally */
+ const FLUSHING_INTERNAL = 'flush';
+
/**
* A string describing the current software version, and possibly
* other details in a user-friendly way. Will be listed on Special:Version, etc.
*
* @param string $fname Caller name (usually __METHOD__)
* @param callable $callback Callback that issues DB updates
+ * @return mixed $res Result of the callback (since 1.28)
* @throws DBError
* @throws RuntimeException
* @throws UnexpectedValueException
* Begin a transaction. If a transaction is already in progress,
* that transaction will be committed before the new transaction is started.
*
+ * Only call this from code with outer transcation scope.
+ * See https://www.mediawiki.org/wiki/Database_transactions for details.
+ * Nesting of transactions is not supported.
+ *
* Note that when the DBO_TRX flag is set (which is usually the case for web
* requests, but not for maintenance scripts), any previous database query
* will have started a transaction automatically.
* automatically because of the DBO_TRX flag.
*
* @param string $fname
+ * @param string $mode A situationally valid IDatabase::TRANSACTION_* constant [optional]
* @throws DBError
*/
- public function begin( $fname = __METHOD__ );
+ public function begin( $fname = __METHOD__, $mode = self::TRANSACTION_EXPLICIT );
/**
* Commits a transaction previously started using begin().
* If no transaction is in progress, a warning is issued.
*
+ * Only call this from code with outer transcation scope.
+ * See https://www.mediawiki.org/wiki/Database_transactions for details.
* Nesting of transactions is not supported.
*
* @param string $fname
- * @param string $flush Flush flag, set to 'flush' to disable warnings about
- * explicitly committing implicit transactions, or calling commit when no
- * transaction is in progress.
+ * @param string $flush Flush flag, set to situationally valid IDatabase::FLUSHING_*
+ * constant to disable warnings about explicitly committing implicit transactions,
+ * or calling commit when no transaction is in progress.
*
* This will trigger an exception if there is an ongoing explicit transaction.
*
* Rollback a transaction previously started using begin().
* If no transaction is in progress, a warning is issued.
*
- * No-op on non-transactional databases.
+ * Only call this from code with outer transcation scope.
+ * See https://www.mediawiki.org/wiki/Database_transactions for details.
+ * Nesting of transactions is not supported. If a serious unexpected error occurs,
+ * throwing an Exception is preferrable, using a pre-installed error handler to trigger
+ * rollback (in any case, failure to issue COMMIT will cause rollback server-side).
*
* @param string $fname
- * @param string $flush Flush flag, set to 'flush' to disable warnings about
- * calling rollback when no transaction is in progress. This will silently
- * break any ongoing explicit transaction. Only set the flush flag if you
- * are sure that it is safe to ignore these warnings in your context.
+ * @param string $flush Flush flag, set to a situationally valid IDatabase::FLUSHING_*
+ * constant to disable warnings about calling rollback when no transaction is in
+ * progress. This will silently break any ongoing explicit transaction. Only set the
+ * flush flag if you are sure that it is safe to ignore these warnings in your context.
* @throws DBUnexpectedError
* @since 1.23 Added $flush parameter
*/
/**
* Acquire a named lock, flush any transaction, and return an RAII style unlocker object
*
+ * Only call this from outer transcation scope and when only one DB will be affected.
+ * See https://www.mediawiki.org/wiki/Database_transactions for details.
+ *
* This is suitiable for transactions that need to be serialized using cooperative locks,
* where each transaction can see each others' changes. Any transaction is flushed to clear
* out stale REPEATABLE-READ snapshot data. Once the returned object falls out of PHP scope,
- * any transaction will be committed and the lock will be released.
+ * the lock will be released unless a transaction is active. If one is active, then the lock
+ * will be released when it either commits or rolls back.
*
* If the lock acquisition failed, then no transaction flush happens, and null is returned.
*
/** @var WANObjectCache */
protected $wanCache;
+ /** @var mixed */
+ protected $ticket;
/** @var string|bool Reason all LBs are read-only or false if not */
protected $readOnlyReason = false;
$this->wanCache = WANObjectCache::newEmpty();
}
$this->trxLogger = LoggerFactory::getInstance( 'DBTransaction' );
+ $this->ticket = mt_rand();
}
/**
}
}
+ /**
+ * Get a token asserting that no transaction writes are active
+ *
+ * @param string $fname Caller name (e.g. __METHOD__)
+ * @return mixed A value to pass to commitAndWaitForReplication()
+ * @since 1.28
+ */
+ public function getEmptyTransactionTicket( $fname ) {
+ if ( $this->hasMasterChanges() ) {
+ $this->trxLogger->error( __METHOD__ . ": $fname does not have outer scope." );
+ return null;
+ }
+
+ return $this->ticket;
+ }
+
+ /**
+ * Convenience method for safely running commitMasterChanges()/waitForReplication()
+ *
+ * This will commit and wait unless $ticket indicates it is unsafe to do so
+ *
+ * @param string $fname Caller name (e.g. __METHOD__)
+ * @param mixed $ticket Result of getOuterTransactionScopeTicket()
+ * @param array $opts Options to waitForReplication()
+ * @throws DBReplicationWaitError
+ * @since 1.28
+ */
+ public function commitAndWaitForReplication( $fname, $ticket, array $opts = [] ) {
+ if ( $ticket !== $this->ticket ) {
+ $logger = LoggerFactory::getInstance( 'DBPerformance' );
+ $logger->error( __METHOD__ . ": cannot commit; $fname does not have outer scope." );
+ return;
+ }
+
+ $this->commitMasterChanges( $fname );
+ $this->waitForReplication( $opts );
+ }
+
/**
* Disable the ChronologyProtector for all load balancers
*
}
$serverInfo['hostName'] = $serverName;
$serverInfo['load'] = $load;
+ $serverInfo += [ 'flags' => DBO_DEFAULT ];
+
$servers[] = $serverInfo;
}
} else {
$server['slave'] = true;
}
+ $server += [ 'flags' => DBO_DEFAULT ];
}
} else {
global $wgDBserver, $wgDBuser, $wgDBpassword, $wgDBname, $wgDBtype, $wgDebugDumpSql;
*/
public function commitAll( $fname = __METHOD__ ) {
$this->forEachOpenConnection( function ( DatabaseBase $conn ) use ( $fname ) {
- $conn->commit( $fname, 'flush' );
+ $conn->commit( $fname, IDatabase::FLUSHING_ALL_PEERS );
} );
}
public function commitMasterChanges( $fname = __METHOD__ ) {
$this->forEachOpenMasterConnection( function ( DatabaseBase $conn ) use ( $fname ) {
if ( $conn->writesOrCallbacksPending() ) {
- $conn->commit( $fname, 'flush' );
+ $conn->commit( $fname, IDatabase::FLUSHING_ALL_PEERS );
}
} );
}
foreach ( $conns2[$masterIndex] as $conn ) {
if ( $conn->trxLevel() && $conn->writesOrCallbacksPending() ) {
try {
- $conn->rollback( $fname, 'flush' );
+ $conn->rollback( $fname, IDatabase::FLUSHING_ALL_PEERS );
} catch ( DBError $e ) {
MWExceptionHandler::logException( $e );
$failedServers[] = $conn->getServer();
* subclasses can override the beginTransaction() and commitTransaction() methods.
*/
abstract class DataUpdate implements DeferrableUpdate {
+ /** @var mixed Result from LBFactory::getEmptyTransactionTicket() */
+ protected $ticket;
+
public function __construct() {
// noop
}
+ /**
+ * @param mixed $ticket Result of getEmptyTransactionTicket()
+ * @since 1.28
+ */
+ public function setTransactionTicket( $ticket ) {
+ $this->ticket = $ticket;
+ }
+
/**
* Begin an appropriate transaction, if any.
* This default implementation does nothing.
public function doUpdate() {
$config = RequestContext::getMain()->getConfig();
$batchSize = $config->get( 'UpdateRowsPerQuery' );
+ $factory = wfGetLBFactory();
// Page may already be deleted, so don't just getId()
$id = $this->pageId;
foreach ( $catBatches as $catBatch ) {
$this->page->updateCategoryCounts( [], $catBatch, $id );
if ( count( $catBatches ) > 1 ) {
- $this->mDb->commit( __METHOD__, 'flush' );
- wfGetLBFactory()->waitForReplication( [ 'wiki' => $this->mDb->getWikiID() ] );
+ $factory->commitAndWaitForReplication(
+ __METHOD__, $this->ticket, [ 'wiki' => $this->mDb->getWikiID() ]
+ );
}
}
foreach ( $rcIdBatches as $rcIdBatch ) {
$this->mDb->delete( 'recentchanges', [ 'rc_id' => $rcIdBatch ], __METHOD__ );
if ( count( $rcIdBatches ) > 1 ) {
- $this->mDb->commit( __METHOD__, 'flush' );
- wfGetLBFactory()->waitForReplication( [ 'wiki' => $this->mDb->getWikiID() ] );
+ $factory->commitAndWaitForReplication(
+ __METHOD__, $this->ticket, [ 'wiki' => $this->mDb->getWikiID() ]
+ );
}
}
}
private function batchDeleteByPK( $table, array $conds, array $pk, $bSize ) {
$dbw = $this->mDb; // convenience
+ $factory = wfGetLBFactory();
$res = $dbw->select( $table, $pk, $conds, __METHOD__ );
$pkDeleteConds = [];
$pkDeleteConds[] = $this->mDb->makeList( (array)$row, LIST_AND );
if ( count( $pkDeleteConds ) >= $bSize ) {
$dbw->delete( $table, $dbw->makeList( $pkDeleteConds, LIST_OR ), __METHOD__ );
- $dbw->commit( __METHOD__, 'flush' );
- wfGetLBFactory()->waitForReplication( [ 'wiki' => $dbw->getWikiID() ] );
+ $factory->commitAndWaitForReplication(
+ __METHOD__, $this->ticket, [ 'wiki' => $this->mDb->getWikiID() ]
+ );
$pkDeleteConds = [];
}
}
*/
private function incrTableUpdate( $table, $prefix, $deletions, $insertions ) {
$bSize = RequestContext::getMain()->getConfig()->get( 'UpdateRowsPerQuery' );
+ $factory = wfGetLBFactory();
if ( $table === 'page_props' ) {
$fromField = 'pp_page';
foreach ( $deleteWheres as $deleteWhere ) {
$this->mDb->delete( $table, $deleteWhere, __METHOD__ );
- $this->mDb->commit( __METHOD__, 'flush' );
- wfGetLBFactory()->waitForReplication( [ 'wiki' => $this->mDb->getWikiID() ] );
+ $factory->commitAndWaitForReplication(
+ __METHOD__, $this->ticket, [ 'wiki' => $this->mDb->getWikiID() ]
+ );
}
$insertBatches = array_chunk( $insertions, $bSize );
foreach ( $insertBatches as $insertBatch ) {
$this->mDb->insert( $table, $insertBatch, __METHOD__, 'IGNORE' );
- $this->mDb->commit( __METHOD__, 'flush' );
- wfGetLBFactory()->waitForReplication( [ 'wiki' => $this->mDb->getWikiID() ] );
+ $factory->commitAndWaitForReplication(
+ __METHOD__, $this->ticket, [ 'wiki' => $this->mDb->getWikiID() ]
+ );
}
if ( count( $insertions ) ) {
* @param array $values
* @return string
*/
- protected function getInputHTMLForKey( $key, $values ) {
+ protected function getInputHTMLForKey( $key, array $values ) {
$displayFormat = isset( $this->mParams['format'] )
? $this->mParams['format']
: $this->mParent->getDisplayFormat();
'url' => 'https://creativecommons.org/publicdomain/zero/1.0/',
'icon' => '$wgResourceBasePath/resources/assets/licenses/cc-0.png',
],
- 'pd' => [
- 'url' => '',
- 'icon' => '$wgResourceBasePath/resources/assets/licenses/public-domain.png',
- ],
'gfdl' => [
'url' => 'https://www.gnu.org/copyleft/fdl.html',
'icon' => '$wgResourceBasePath/resources/assets/licenses/gnu-fdl.png',
return false;
}
// Clear any stale REPEATABLE-READ snapshot
- $dbr->commit( __METHOD__, 'flush' );
+ wfGetLBFactory()->commitAll( __METHOD__ );
$cutoffUnix = wfTimestamp( TS_UNIX, $this->params['revTimestamp'] );
// Using ENQUEUE_FUDGE_SEC handles jobs inserted out of revision order due to the delay
}
$dbw = wfGetDB( DB_MASTER );
+ $factory = wfGetLBFactory();
+ $ticket = $factory->getEmptyTransactionTicket( __METHOD__ );
+
$catMembChange = new CategoryMembershipChange( $title, $newRev );
$catMembChange->checkTemplateLinks();
$categoryTitle = Title::makeTitle( NS_CATEGORY, $categoryName );
$catMembChange->triggerCategoryAddedNotification( $categoryTitle );
if ( $insertCount++ && ( $insertCount % $batchSize ) == 0 ) {
- $dbw->commit( __METHOD__, 'flush' );
- wfGetLBFactory()->waitForReplication();
+ $factory->commitAndWaitForReplication( __METHOD__, $ticket );
}
}
$categoryTitle = Title::makeTitle( NS_CATEGORY, $categoryName );
$catMembChange->triggerCategoryRemovedNotification( $categoryTitle );
if ( $insertCount++ && ( $insertCount++ % $batchSize ) == 0 ) {
- $dbw->commit( __METHOD__, 'flush' );
- wfGetLBFactory()->waitForReplication();
+ $factory->commitAndWaitForReplication( __METHOD__, $ticket );
}
}
}
* @file
* @ingroup JobQueue
*/
+use \MediaWiki\MediaWikiServices;
/**
* Job to prune link tables for pages that were deleted
return false;
}
+ $factory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
$timestamp = isset( $this->params['timestamp'] ) ? $this->params['timestamp'] : null;
-
$page = WikiPage::factory( $this->title ); // title when deleted
+
$update = new LinksDeletionUpdate( $page, $pageId, $timestamp );
+ $update->setTransactionTicket( $factory->getEmptyTransactionTicket( __METHOD__ ) );
DataUpdate::runUpdates( [ $update ] );
return true;
$touchTimestamp = wfTimestampNow();
$dbw = wfGetDB( DB_MASTER );
+ $factory = wfGetLBFactory();
+ $ticket = $factory->getEmptyTransactionTicket( __METHOD__ );
// Update page_touched (skipping pages already touched since the root job).
// Check $wgUpdateRowsPerQuery for sanity; batch jobs are sized by that already.
foreach ( array_chunk( $pageIds, $wgUpdateRowsPerQuery ) as $batch ) {
- $dbw->commit( __METHOD__, 'flush' );
- wfGetLBFactory()->waitForReplication();
+ $factory->commitAndWaitForReplication( __METHOD__, $ticket );
$dbw->update( 'page',
[ 'page_touched' => $dbw->timestamp( $touchTimestamp ) ],
return; // already in progress
}
+ $factory = wfGetLBFactory();
+ $ticket = $factory->getEmptyTransactionTicket( __METHOD__ );
$cutoff = $dbw->timestamp( time() - $wgRCMaxAge );
do {
$rcIds = $dbw->selectFieldValues( 'recentchanges',
);
if ( $rcIds ) {
$dbw->delete( 'recentchanges', [ 'rc_id' => $rcIds ], __METHOD__ );
- }
- // Commit in chunks to avoid slave lag
- $dbw->commit( __METHOD__, 'flush' );
-
- if ( count( $rcIds ) === $wgUpdateRowsPerQuery ) {
// There might be more, so try waiting for slaves
try {
- wfGetLBFactory()->waitForReplication( [ 'timeout' => 3 ] );
+ $factory->commitAndWaitForReplication(
+ __METHOD__, $ticket, [ 'timeout' => 3 ]
+ );
} catch ( DBReplicationWaitError $e ) {
// Another job will continue anyway
break;
// JobRunner uses DBO_TRX, but doesn't call begin/commit itself;
// onTransactionIdle() will run immediately since there is no trx.
$dbw->onTransactionIdle( function() use ( $dbw, $days, $window ) {
+ $factory = wfGetLBFactory();
+ $ticket = $factory->getEmptyTransactionTicket( __METHOD__ );
// Avoid disconnect/ping() cycle that makes locks fall off
$dbw->setSessionOptions( [ 'connTimeout' => 900 ] );
}
foreach ( array_chunk( $newRows, 500 ) as $rowBatch ) {
$dbw->insert( 'querycachetwo', $rowBatch, __METHOD__ );
- wfGetLBFactory()->waitForReplication();
+ $factory->commitAndWaitForReplication( __METHOD__, $ticket );
}
}
$parserOutput
);
+ $factory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+ $ticket = $factory->getEmptyTransactionTicket( __METHOD__ );
foreach ( $updates as $key => $update ) {
+ $update->setTransactionTicket( $ticket );
// FIXME: This code probably shouldn't be here?
// Needed by things like Echo notifications which need
// to know which user caused the links update
return $ok;
}
+ public function changeTTL( $key, $expiry = 0 ) {
+ list( $serverIndex, $tableName ) = $this->getTableByKey( $key );
+ try {
+ $db = $this->getDB( $serverIndex );
+ $db->update(
+ $tableName,
+ [ 'exptime' => $db->timestamp( $this->convertExpiry( $expiry ) ) ],
+ [ 'keyname' => $key, 'exptime > ' . $db->addQuotes( $db->timestamp( time() ) ) ],
+ __METHOD__
+ );
+ if ( $db->affectedRows() == 0 ) {
+ return false;
+ }
+ } catch ( DBError $e ) {
+ $this->handleWriteError( $e, $serverIndex );
+ return false;
+ }
+
+ return true;
+ }
+
/**
* @param IDatabase $db
* @param string $exptime
/** @var ResourceLoader */
private $resourceLoader;
+ /** @var string|null */
+ private $target;
+
/** @var array */
private $config = [];
/**
* @param ResourceLoaderContext $context
+ * @param aray $target [optional] Custom 'target' parameter for the startup module
*/
- public function __construct( ResourceLoaderContext $context ) {
+ public function __construct( ResourceLoaderContext $context, $target = null ) {
$this->context = $context;
$this->resourceLoader = $context->getResourceLoader();
+ $this->target = $target;
}
/**
}
// Async scripts. Once the startup is loaded, inline RLQ scripts will run.
- $chunks[] = $this->getLoad( 'startup', ResourceLoaderModule::TYPE_SCRIPTS );
+ // Pass-through a custom target from OutputPage (T143066).
+ $startupQuery = $this->target ? [ 'target' => $this->target ] : [];
+ $chunks[] = $this->getLoad(
+ 'startup',
+ ResourceLoaderModule::TYPE_SCRIPTS,
+ $startupQuery
+ );
return WrappedStringList::join( "\n", $chunks );
}
return self::makeContext( $this->context, $group, $type );
}
- private function getLoad( $modules, $only ) {
- return self::makeLoad( $this->context, (array)$modules, $only );
+ private function getLoad( $modules, $only, array $extraQuery = [] ) {
+ return self::makeLoad( $this->context, (array)$modules, $only, $extraQuery );
}
private static function makeContext( ResourceLoaderContext $mainContext, $group, $type,
$this->loadFromDefinition();
return $this->position;
}
+
+ /**
+ * @return string
+ */
+ public function getType() {
+ return self::LOAD_STYLES;
+ }
}
$tpl->set( 'sidebar', $this->buildSidebar() );
$tpl->set( 'nav_urls', $this->buildNavUrls() );
+ // Do this last in case hooks above add bottom scripts
+ $tpl->set( 'bottomscripts', $this->bottomScripts() );
+
// Set the head scripts near the end, in case the above actions resulted in added scripts
$tpl->set( 'headelement', $out->headElement( $this ) );
// See Skin::afterContentHook() for further documentation.
$tpl->set( 'dataAfterContent', $this->afterContentHook() );
- // Do this last in case hooks above add bottom scripts
- $tpl->set( 'bottomscripts', $this->bottomScripts() );
-
return $tpl;
}
}
/**
- * Like stashFile(), but respects extensions' wishes to prevent the stashing.
+ * Like stashFile(), but respects extensions' wishes to prevent the stashing. verifyUpload() must
+ * be called before calling this method (unless $isPartial is true).
*
* Upload stash exceptions are also caught and converted to an error status.
*
* @since 1.28
* @param User $user
+ * @param bool $isPartial Pass `true` if this is a part of a chunked upload (not a complete file).
* @return Status If successful, value is an UploadStashFile instance
*/
- public function tryStashFile( User $user ) {
- $props = $this->mFileProps;
- $error = null;
- Hooks::run( 'UploadStashFile', [ $this, $user, $props, &$error ] );
- if ( $error ) {
- if ( !is_array( $error ) ) {
- $error = [ $error ];
+ public function tryStashFile( User $user, $isPartial = false ) {
+ if ( !$isPartial ) {
+ $error = $this->runUploadStashFileHook( $user );
+ if ( $error ) {
+ return call_user_func_array( 'Status::newFatal', $error );
}
- return call_user_func_array( 'Status::newFatal', $error );
}
try {
$file = $this->doStashFile( $user );
}
}
+ /**
+ * @param User $user
+ * @return array|null Error message and parameters, null if there's no error
+ */
+ protected function runUploadStashFileHook( User $user ) {
+ $props = $this->mFileProps;
+ $error = null;
+ Hooks::run( 'UploadStashFile', [ $this, $user, $props, &$error ] );
+ if ( $error ) {
+ if ( !is_array( $error ) ) {
+ $error = [ $error ];
+ }
+ }
+ return $error;
+ }
+
/**
* If the user does not supply all necessary information in the first upload
* form submission (either by accident or by design) then we may want to
/**
* Setup local pointers to stash, repo and user (similar to UploadFromStash)
*
- * @param User|null $user Default: null
+ * @param User $user
* @param UploadStash|bool $stash Default: false
* @param FileRepo|bool $repo Default: false
*/
- public function __construct( $user = null, $stash = false, $repo = false ) {
- // user object. sometimes this won't exist, as when running from cron.
+ public function __construct( User $user, $stash = false, $repo = false ) {
$this->user = $user;
if ( $repo ) {
// Update the mTempPath and mLocalFile
// (for FileUpload or normal Stash to take over)
$tStart = microtime( true );
- $this->mLocalFile = parent::doStashFile( $this->user );
+ // This is a re-implementation of UploadBase::tryStashFile(), we can't call it because we
+ // override doStashFile() with completely different functionality in this class...
+ $error = $this->runUploadStashFileHook( $this->user );
+ if ( $error ) {
+ call_user_func_array( [ $status, 'fatal' ], $error );
+ return $status;
+ }
+ try {
+ $this->mLocalFile = parent::doStashFile( $this->user );
+ } catch ( UploadStashException $e ) {
+ $status->fatal( 'uploadstash-exception', get_class( $e ), $e->getMessage() );
+ return $status;
+ }
+
$tAmount = microtime( true ) - $tStart;
$this->mLocalFile->setLocalReference( $tmpFile ); // reuse (e.g. for getImageInfo())
wfDebugLog( 'fileconcatenate', "Stashed combined file ($i chunks) in $tAmount seconds." );
$this->getOffset() . ' inx:' . $this->getChunkIndex() . "\n" );
$dbw = $this->repo->getMasterDB();
- // Use a quick transaction since we will upload the full temp file into shared
- // storage, which takes time for large files. We don't want to hold locks then.
$dbw->update(
'uploadstash',
[
[ 'us_key' => $this->mFileKey ],
__METHOD__
);
- $dbw->commit( __METHOD__, 'flush' );
}
/**
return $this->mFileProps['sha1'];
}
- /*
- * protected function verifyFile() inherited
- */
-
- /**
- * Stash the file.
- *
- * @param User $user
- * @return UploadStashFile
- */
- protected function doStashFile( User $user = null ) {
- // replace mLocalFile with an instance of UploadStashFile, which adds some methods
- // that are useful for stashed files.
- $this->mLocalFile = parent::doStashFile( $user );
-
- return $this->mLocalFile;
- }
-
/**
* Remove a temporarily kept file stashed by saveTempUploadedFile().
* @return bool Success
* login credentials aren't being hijacked with a foreign form
* submission.
*
+ * The $salt for 'edit' and 'csrf' tokens is the default (empty string).
+ *
* @since 1.19
* @param string|array $salt Array of Strings Optional function-specific data for hashing
* @param WebRequest|null $request WebRequest object to use or null to use $wgRequest
* @defgroup Language Language
*/
-if ( !defined( 'MEDIAWIKI' ) ) {
- echo "This file is part of MediaWiki, it is not a valid entry point.\n";
- exit( 1 );
-}
-
use CLDRPluralRuleParser\Evaluator;
/**
"alllogstext": "{{SITENAME}} üçün bütün mövcud qeydlərin birgə göstərişi.\nQeyd növü, istifadəçi adı və ya təsir edilmiş səhifəni seçməklə daha spesifik ola bilərsiniz.",
"logempty": "Jurnalda uyğun qeyd tapılmadı.",
"log-title-wildcard": "Bu mətnlə başlayan başlıqları axtar",
+ "checkbox-select": "Seçin: $1",
+ "checkbox-all": "Hamısı",
+ "checkbox-none": "Heç biri",
+ "checkbox-invert": "Çevir",
"allpages": "Bütün səhifələr",
"nextpage": "Sonrakı səhifə ($1)",
"prevpage": "Əvvəlki səhifə ($1)",
"right-changetags": "নির্দিষ্ট সংস্করণ এবং দীর্ঘ সম্পাদনাগুলোতে [[Special:Tags|ট্যাগ]] সংযোজন ও অপসারণ করুন",
"right-deletechangetags": "ডাটাবেজ থেকে [[Special:Tags|ট্যাগ]] অপসারণ করা",
"grant-group-email": "ইমেইল পাঠান",
+ "grant-group-private-information": "আপনার সম্পর্কিত ব্যক্তিগত তথ্যে প্রবেশাধিকার পায়",
"grant-group-other": "বিবিধ কার্যকলাপ",
"grant-createaccount": "অ্যাকাউন্ট তৈরি করুন",
"grant-createeditmovepage": "পাতা তৈরি, সম্পাদনা এবং স্থানান্তর করুন",
"grant-editmyoptions": "আপনার ব্যবহারকারী পছন্দসমূহ সম্পাদনা করুন",
"grant-editmywatchlist": "আপনার নজরতালিকা সম্পাদনা করুন",
"grant-editprotected": "সংরক্ষিত পাতা সম্পাদনা করুন",
+ "grant-privateinfo": "ব্যক্তিগত তথ্যে প্রবেশাধিকার",
"grant-sendemail": "অন্য ব্যবহারকারীকে ইমেইল পাঠান",
"grant-uploadfile": "নতুন ফাইল আপলোড করুন",
"grant-basic": "মৌলিক অধিকার",
"editlink": "دەستکاری",
"viewsourcelink": "بینینی سەرچاوە",
"editsectionhint": "دەستکاریکردنی بەش: $1",
- "toc": "Ù\86اÙ\88Û\95Ú\95Û\86Ú©",
+ "toc": "Ù¾Û\8eرست",
"showtoc": "نیشانیبدە",
"hidetoc": "بیشارەوە",
"collapsible-collapse": "کۆی بکەوە",
"grant-group-high-volume": "Velkoobjemové činnosti",
"grant-group-customization": "Nastavení a přizpůsobení",
"grant-group-administration": "Provádění správcovských činností",
- "grant-group-private-information": "Zpřístupnit soukromá data o vás",
+ "grant-group-private-information": "Přístup k soukromým údajům o vás",
"grant-group-other": "Různé činnosti",
"grant-blockusers": "Blokovat a odblokovávat uživatele",
"grant-createaccount": "Zakládat účty",
"grant-highvolume": "Hromadné editace",
"grant-oversight": "Skrývat uživatele a utajovat revize",
"grant-patrol": "Patrolovat změny stránek",
+ "grant-privateinfo": "Přístup k soukromým údajům",
"grant-protect": "Zamykat a odemykat stránky",
"grant-rollback": "Vracet editace zpět",
"grant-sendemail": "Posílat e-maily ostatním uživatelům",
"uploadstash-errclear": "Soubory se nepodařilo vymazat.",
"uploadstash-refresh": "Aktualizovat seznam souborů",
"uploadstash-thumbnail": "zobrazit náhled",
+ "uploadstash-exception": "Načtený soubor se nepodařilo uložit do skrýše ($1): „$2“.",
"invalid-chunk-offset": "Neplatný posun bloku",
"img-auth-accessdenied": "Přístup odepřen",
"img-auth-nopathinfo": "Chybí PATH_INFO.\nVáš server není nastaven tak, aby tuto informaci poskytoval.\nMožná funguje pomocí CGI a img_auth na něm nemůže fungovat.\nVizte https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization.",
"trackingcategories-name": "Název hlášení",
"trackingcategories-desc": "Kritéria pro vložení do kategorie",
"restricted-displaytitle-ignored": "Stránky s ignorovanými zobrazovanými názvy",
- "restricted-displaytitle-ignored-desc": "Stránky obsahující příkaz <code><nowiki>{{DISPLAYTITLE}}</nowiki></code>, který se ignoruje, neboť není ekvivalentní skutečnému názvu stránky.",
+ "restricted-displaytitle-ignored-desc": "Stránka obsahuje příkaz <code><nowiki>{{DISPLAYTITLE}}</nowiki></code>, který se ignoruje, neboť není ekvivalentní skutečnému názvu stránky.",
"noindex-category-desc": "Stránka není indexována roboty, protože obsahuje kouzelné slovo <code><nowiki>__NOINDEX__</nowiki></code> a je ve jmenném prostoru, ve kterém je tento příznak dovolen.",
"index-category-desc": "Stránka obsahuje kouzelné slovo <code><nowiki>__INDEX__</nowiki></code> (a je ve jmenném prostoru, ve kterém je tento příznak dovolen), takže je indexována roboty, přestože by normálně nebyla.",
"post-expand-template-inclusion-category-desc": "Stránka je po rozbalení všech šablon větší než <code>$wgMaxArticleSize</code>, takže některé šablony rozbaleny nebyly.",
"anontalk": "Werênayış",
"navigation": "Pusula",
"and": " u",
- "qbfind": "Bıvêne",
+ "qbfind": "Bıvin",
"qbbrowse": "Çım ra viyarne",
"qbedit": "Bıvurne",
"qbpageoptions": "Ena pele",
"view-foreign": "$1 de bıvêne",
"edit": "Bıvurne",
"edit-local": "Şınasnayışê lokali bıvurne",
- "create": "Vıraze",
+ "create": "Bıvıraz",
"create-local": "Şınasnayışê lokali cı ke",
"editthispage": "Ena pele bıvurne",
"create-this-page": "Na pele bınuse",
"talkpagelinktext": "werênayış",
"specialpage": "Pela xısusiye",
"personaltools": "Hacetê şexsiy",
- "articlepage": "Pela zerreki bıvêne",
+ "articlepage": "Pera zerreki bıvin",
"talk": "Werênayış",
"views": "Asayışi",
"toolbox": "Haceti",
"imagepage": "Pera dosya bıasne",
"mediawikipage": "Pera mesaci bıasne",
"templatepage": "Pera şabloni bıasne",
- "viewhelppage": "Pela peşti bıvêne",
+ "viewhelppage": "Pera peşti bıvin",
"categorypage": "Pela kategoriya bıasne",
"viewtalkpage": "Werênayışi bıvêne",
"otherlanguages": "Zıwananê binan de",
"redirectedfrom": "($1 ra kırışı yê)",
"redirectpagesub": "Pela berdışi",
"redirectto": "Beno hetê:",
- "lastmodifiedat": "Ena pele tewr peyên roca $1, saeta $2 de biye rocane.",
+ "lastmodifiedat": "Per roca $1, sehat $2 de biye neye.",
"viewcount": "Ena pele {{PLURAL:$1|rae|$1 rey}} vêniya.",
"protectedpage": "Pela pawıtiye",
"jumpto": "Şo be:",
"confirmable-yes": "Eya",
"confirmable-no": "Nê",
"thisisdeleted": "Bıvêne ya zi $1 peyser biya?",
- "viewdeleted": "$1 bıvêne?",
+ "viewdeleted": "$1 bıvin?",
"restorelink": "{{PLURAL:$1|jew vurnayış besteriya|$1 vurnayışi besteriyaye}}",
"feedlinks": "Warikerdış:",
"feed-invalid": "Qeydey cıresnayışê beğşi nêvêreno.",
"perfcached": "Datay cı ver hazır biye. No semedê ra nıkayin niyo! tewr zaf {{PLURAL:$1|netice|$1 netice}} debêno de",
"perfcachedts": "Cêr de malumatê nımıteyi esti, demdê newe kerdışo peyın: $1. Tewr zaf {{PLURAL:$4|netice|$4 neticey cı}} debyayo de",
"querypage-no-updates": "Rocanebiyayışê na pele nıka cadayiyê.\nDayiyi tiya nıka newe nêbenê.",
- "viewsource": "Çımey bıvêne",
+ "viewsource": "Çemi bıvin",
"viewsource-title": "Cı geyrayışê $1'i bıvin",
"actionthrottled": "Kerden peysnaya",
"actionthrottledtext": "Riyê tedbirê anti-spami ra, wextê do kılmek de şıma nê fealiyeti nêşkenê zaf zêde bıkerê, şıma ki no hedi viyarna ra.\nÇend deqey ra tepeya reyna bıcerrebnên.",
"nologinlink": "Yew hesab ake",
"createaccount": "Hesab vıraze",
"gotaccount": "Hesabê şıma esto? '''$1'''.",
- "gotaccountlink": "Cı kewe",
+ "gotaccountlink": "Cıkewtış",
"userlogin-resetlink": "Melumatê cıkewtışi xo vira kerdê?",
"userlogin-resetpassword-link": "Parola xo kerda xo vira?",
"userlogin-helplink2": "Heqa qeydbiyayışi de peşti bıgêrên",
"botpasswords-label-appid": "Nameyê boti:",
"botpasswords-label-create": "Vıraze",
"botpasswords-label-update": "Rocane ke",
- "botpasswords-label-cancel": "Bıtexelne",
+ "botpasswords-label-cancel": "İbtal ke",
"botpasswords-label-delete": "Bestere",
"botpasswords-label-resetpassword": "Parola raçarne",
"botpasswords-label-grants-column": "Dayen",
"resetpass_forbidden": "parolayi nêvuryayi",
"resetpass-no-info": "şıma gani hesab akere u hona bıeşke bırese cı",
"resetpass-submit-loggedin": "Parola bıvurne",
- "resetpass-submit-cancel": "Bıtexelne",
+ "resetpass-submit-cancel": "İbtal ke",
"resetpass-wrong-oldpass": "parolayo parola maqbul niyo.\nşıma ya parolaye xo vurnayo ya zi parolayo muwaqqat waşto.",
"resetpass-recycled": "Parolaya şımaya newiye wa paroloya şımaya verêne ra ferqıne bo.",
"resetpass-temp-emailed": "E postaya rışyayê yubkoda şıma ronıştış akerdo. Ronıştışi xo temammkerdışi rê yu parolaya newi lazım a",
"hr_tip": "Xeta verardiye (teserrufın bıgureyne/bıxebetne)",
"summary": "Xulasa:",
"subject": "Mewzu:",
- "minoredit": "No yew vurnayışo werdiyo",
- "watchthis": "Ena pele seyr ke",
- "savearticle": "Pele qeyd ke",
+ "minoredit": "Vurriyayışo werdiyo",
+ "watchthis": "Seyr kı",
+ "savearticle": "Qeyd kı",
"savechanges": "Vurnayışan qeyd ke",
"publishpage": "Perer bıhesırne",
"publishchanges": "Vurnayışa vıla ke",
"accmailtext": "[[User talk:$1|$1]] parolayo ke raşt ameyo şırawiyo na adres $2.\n\nQey na hesabê newe parola, cıkewtış dıma şıma eşkeni na qısım de ''[[Special:ChangePassword|parola bıvurn]]'' bıvurni.",
"newarticle": "(Newe)",
"newarticletext": "To yew gıre tıkna be ra yew pela ke hewna çıniya.\nSeba afernayışê pele ra, qutiya metnê cêrêni bıgurene (seba melumati qaytê [$1 pela peşti] ke).\nEke be ğeletine ameya tiya, wa gocega <strong>peyser</strong>i programê xo de bıtıkne.",
- "anontalkpagetext": "----''No pel, pel o karbero hesab a nêkerdeyan o, ya zi karbero hesab akerdeyan o labele pê hesabê xo nêkewto de. No sebeb ra ma IP adres şuxulneni û ney IP adresan herkes eşkeno bıvino. Eke şıma qayil niye ina bo xo ri [[Special:CreateAccount|yew hesab bıvıraze]] veyaxut [[Special:UserLogin|hesab akere]].''",
+ "anontalkpagetext": "----''Na per, perêk kı karbero hesab a nêkerdeyan o, ya zi karbero hesab akerdeyan o labele pê hesabê xo nêkewto de. No sebeb ra ma IP adres xebetneno û ney IP adresan herkes nêşeno bıvino. Eke şıma qayil niye ina bo xorê [[Special:CreateAccount|yew hesab bıvıraze]] veya xut [[Special:UserLogin|hesab akere]].''",
"noarticletext": "Ena pele de hewna theba çıniyo.\nTı şenê zerreyê pelanê binan de [[Special:Search/{{PAGENAME}}|qandê sernameyê ena pele cı geyre]],\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} qeydan miyan de cı geyre],\nya zi [{{fullurl:{{FULLPAGENAME}}|action=edit}} ena pele vıraze]</span>.{{MediaWiki mesaca pera newi}}",
"noarticletext-nopermission": "Ena pele de hewna theba çıniyo.\nTı şenay zerreyê pelanê binan de [[Special:Search/{{PAGENAME}}|seba sernameyê na pele cı geyre]], ya zi <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} qeydan miyan de cı geyre]</span>, ema destur çıniyo ke na pele vırazê.",
"missing-revision": "Rewizyonê name dê pela da #$1 \"{{FULLPAGENAME}}\" dı çıniyo.\n\nNo normal de tarix dê pelanê besterneyan dı ena xırabin asena.\nDetayê besternayışi [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} tiya dı] aseno.",
"next-page": "Pela peyên",
"prevn-title": "$1o verên {{PLURAL:$1|netice|neticeyan}}",
"nextn-title": "$1o ke yeno {{PLURAL:$1|netice|neticey}}",
- "shown-title": "bimocne $1î {{PLURAL:$1|netice|neticeyan}} ser her pel",
+ "shown-title": "Herg per sero $1 {{PLURAL:$1|netici|netica}} bıasne",
"viewprevnext": "($1 {{int:pipe-separator}} $2) ($3) bıvênên",
"searchmenu-exists": "''Ena 'Wikipediya de ser \"[[:$1]]\" yew pel esto'''",
"searchmenu-new": "<strong>Na wiki de pela \"[[:$1]]\" vıraze!</strong> {{PLURAL:$2|0=|Sewbina pela ke şıma geyrayê cı aye bıvênê.|Yew zi neticanê cıgeyrayışê xo bıvênê.}}",
"rightslogtext": "Ena listeyê loganê ke heqqa karbaranî mucneno.",
"action-read": "ena pela wanayış",
"action-edit": "ena pela bıvurnê",
- "action-createpage": "na pele vıraze",
+ "action-createpage": "na perer bıvıraz",
"action-createtalk": "pelanê werênayışi bıvıraze",
"action-createaccount": "hesabê nê karberi bıvıraze",
"action-autocreateaccount": "nê hesabê karberiyê teberi otomatik vıraze",
"enhancedrc-history": "tarix",
"recentchanges": "Vurriyayışê peyêni",
"recentchanges-legend": "Tercihê vurnayışanê peyênan",
- "recentchanges-summary": "Ena pele de wiki sero vurriyayışanê peyênan teqib ke.",
+ "recentchanges-summary": "Wiki sero vurriyayışê peyêni asenê.",
"recentchanges-noresult": "Goreyê kriteranê kıfşkerdeyan ra qet yew vurnayış nêvêniya.",
"recentchanges-feed-description": "Ena feed dı vurnayişanê tewr peniyan teqip bık.",
"recentchanges-label-newpage": "Enê vurnayışi ra yew pela newiye vıraziye",
- "recentchanges-label-minor": "No yew vurnayışo werdiyo",
+ "recentchanges-label-minor": "Vurriyayışo werdiyo",
"recentchanges-label-bot": "Eno vurnayış terefê yew boti ra vıraziyo",
"recentchanges-label-unpatrolled": "Eno vurnayış hewna dewriya nêbiyo",
"recentchanges-label-plusminus": "Ebadê pele de bazê bayti de vayeyê cı",
"rcshowhidecategorization": "kategorizasyonê pele $1",
"rcshowhidecategorization-show": "Bıasne",
"rcshowhidecategorization-hide": "Bınımne",
- "rclinks": "Peyniya $2 rocan de $1 vurriyayışan bımocne <br />$3",
+ "rclinks": "Peyniya $2 rocan de $1 vurriyayışê <br />$3 asenê",
"diff": "ferq",
"hist": "verên",
"hide": "Bınımne",
"removedwatchtext": "Ena pela \"[[:$1]]\" biya wedariya [[Special:Watchlist|listeyê seyr-kerdışi şıma]].",
"removedwatchtext-short": "Pera $1`i listeya seyran de şıma ra wedari yê",
"watch": "Seyr ke",
- "watchthispage": "Ena pele seyr ke",
+ "watchthispage": "Seyr kı",
"unwatch": "Teqib meke",
"unwatchthispage": "temaşa kerdışê peli vındarn.",
"notanarticle": "mebhesê peli niyo",
"pagesize": "(bitî)",
"restriction-edit": "Bıvurne",
"restriction-move": "Bıkırış",
- "restriction-create": "Vıraze",
+ "restriction-create": "Bıvıraz",
"restriction-upload": "Bar ke",
"restriction-level-sysop": "tam pawiyayo",
"restriction-level-autoconfirmed": "nêm pawiyayo",
"undeleterevision-missing": "revizyonê nemeqbul u vindbiyayeyi.\nRevizyoni ya hewn a biyê ya arşiw ra veciyayê ya zi cıresayişê şımayi şaş o.",
"undelete-nodiff": "revizyonê verıni nidiya",
"undeletebtn": "Timar bike",
- "undeletelink": "bıvêne/peyser biya",
+ "undeletelink": "bıewni/peyser biya",
"undeleteviewlink": "bıvin",
"undeleteinvert": "Weçinayışi dimlaşt ke",
"undeletecomment": "Sebeb:",
"unblocked-id": "Blokê $1î wedariyayo",
"blocklist": "Karberê kılitbiyayey",
"ipblocklist": "Karberê kılitbiyayey",
- "ipblocklist-legend": "Yew karberê kılitbiyayey bıvêne",
+ "ipblocklist-legend": "Karberê kılit biyayey bıvin",
"blocklist-userblocks": "Kılitkerdışê hesaban bınımne",
"blocklist-tempblocks": "Kılitkerdışan mıweqet bınımne",
"blocklist-addressblocks": "Tenya kılitkerdışanê IPy bınımne",
"articleexists": "Ena nameyê pela database ma dı esta ya zi tı raşt nınuşt. .\nYewna name bınus.",
"cantmove-titleprotected": "şıma nêşkeni yew peli bıhewelnê tiya çunke pawıyeno",
"movetalk": "Pela werênayışiê elaqedare bere",
- "move-subpages": "Pelanê bınênan bıkırışe (heta pela $1)",
- "move-talk-subpages": "Pelanê werênayışiyê bınênan bıkırışe (heta pela $1)",
+ "move-subpages": "Peranê bınênan bıkırış (hetana $1)",
+ "move-talk-subpages": "Bın peranê peranê vatena bıkırış (hetana $1)",
"movepage-page-exists": "maddeya $1i ca ra esta u newe ra otomatikmen nênusyena.",
"movepage-page-moved": "pelê $1i kırışiya pelê $2i.",
"movepage-page-unmoved": "pelê $1i nêkırışiyeno sernameyê $2i.",
"fileduplicatesearch-result-1": "Dosyayê ''$1î'' de hem-kopya çini yo.",
"fileduplicatesearch-result-n": "Dosyayê ''$1î'' de {{PLURAL:$2|1 hem-kopya|$2 hem-kopyayî'}} esto.",
"fileduplicatesearch-noresults": "Ebe namey \"$1\" ra dosya nêdiyayê.",
- "specialpages": "Pelê xısusiyi",
+ "specialpages": "Page bağsey",
"specialpages-note-top": "Kıtabek",
"specialpages-note": "* Pelê xasê normali.\n* <span class=\"mw-specialpagerestricted\">Pelê xasê nımıtey.</span>",
"specialpages-group-maintenance": "Raporê pawıtışi",
"feedback-bugcheck": "Harika! Sadece [xırabina ke $1 ] çınyayışê cı kontrol keno.",
"feedback-bugnew": "Mı qontrol ke. Xetaya newi xeber ke",
"feedback-bugornote": "Jew mersela teferruato teknik esta şıma reca malumatê şıma hazıro se [ $1 jew xırab rapor] bıvinê.Zewbi zi, formê cerê xo rê şenê karfiyê. Vatışê xo pela da \"[ $3 $2 ]\", namey karber dê xoya piya u wasteriya karfiye.",
- "feedback-cancel": "Bıtexelne",
+ "feedback-cancel": "İbtal kı",
"feedback-close": "Biya star",
"feedback-error1": "Xeta: API ra neticey ne vıcyay",
"feedback-error2": "Xeta: Timar kerdış nebı",
"nolinkstoimage": "Aucune page n'utilise ce fichier.",
"morelinkstoimage": "Voir [[Special:WhatLinksHere/$1|plus de liens]] vers ce fichier.",
"linkstoimage-redirect": "$1 (redirection de fichier) $2",
- "duplicatesoffile": "{{PLURAL:$1|Le fichier suivant est un duplicata|Les fichiers suivants sont des duplicatas}} de celui-ci ([[Special:FileDuplicateSearch/$2|plus de détails]]) :",
+ "duplicatesoffile": "{{PLURAL:$1|Le fichier suivant est un doublon|Les $1 fichiers suivants sont des doublons}} de celui-ci ([[Special:FileDuplicateSearch/$2|plus de détails]]) :",
"sharedupload": "Ce fichier provient de : $1. Il peut être utilisé par d'autres projets.",
"sharedupload-desc-there": "Ce fichier provient de : $1. Il peut être utilisé par d'autres projets.\nVeuillez consulter [$2 sa page de description] pour plus d'informations.",
"sharedupload-desc-here": "Ce fichier provient de $1. Il peut être utilisé par d'autres projets.\nSa description sur sa [$2 page de description] est affichée ci-dessous.",
"filedelete-intro-old": "Vous êtes en train d'effacer la version de '''[[Media:$1|$1]]''' du [$4 $2 à $3].",
"filedelete-comment": "Motif :",
"filedelete-submit": "Supprimer",
- "filedelete-success": "'''$1''' a été supprimé.",
- "filedelete-success-old": "La version de '''[[Media:$1|$1]]''' du $2 à $3 a été supprimée.",
- "filedelete-nofile": "'''$1''' n'existe pas.",
- "filedelete-nofile-old": "Il n'existe aucune version archivée de '''$1''' avec les attributs indiqués.",
+ "filedelete-success": "<strong>$1</strong> a été supprimé.",
+ "filedelete-success-old": "La version de <strong>[[Media:$1|$1]]</strong> du $2 à $3 a été supprimée.",
+ "filedelete-nofile": "<strong>$1</strong> n'existe pas.",
+ "filedelete-nofile-old": "Il n'existe aucune version archivée de <strong>$1</strong> avec les attributs indiqués.",
"filedelete-otherreason": "Motif autre / supplémentaire :",
"filedelete-reason-otherlist": "Autre motif",
"filedelete-reason-dropdown": "* Motifs fréquents de suppression de fichiers\n** Violation du droit d'auteur\n** Fichier dupliqué",
- "filedelete-edit-reasonlist": "Modifier les motifs fréquents de suppression",
- "filedelete-maintenance": "La suppression et restauration de fichiers est temporairement désactivée durant la maintenance.",
+ "filedelete-edit-reasonlist": "Modifier les motifs de suppression",
+ "filedelete-maintenance": "La suppression et la restauration de fichiers sont temporairement désactivées durant la maintenance.",
"filedelete-maintenance-title": "Impossible de supprimer le fichier",
"mimesearch": "Recherche par type de contenu MIME",
"mimesearch-summary": "Cette page vous permet de filtrer les fichiers par leur type de contenu MIME.\nEntrée : type_de_contenu/sous-type ou type_de_contenu/*, par ex. <code>image/jpeg</code>.",
"randompage-nopages": "Il n'y a aucune page dans {{PLURAL:$2|l'espace de noms|les espaces de noms}} : $1.",
"randomincategory": "Page au hasard dans la catégorie",
"randomincategory-invalidcategory": "« $1 » n’est pas un nom de catégorie valide.",
- "randomincategory-nopages": "Il n’y a pas de page dans [[:Category:$1]].",
+ "randomincategory-nopages": "Il n’y a pas de pages dans la catégorie [[:Category:$1|$1]].",
"randomincategory-category": "Catégorie :",
"randomincategory-legend": "Page aléatoire dans la catégorie",
"randomincategory-submit": "Lancer",
"undeletehistorynoadmin": "Esta páxina foi borrada.\nO motivo do borrado consta no resumo que aparece a continuación, xunto cos detalles dos usuarios que editaron esta páxina antes da súa eliminación.\nO texto destas revisións eliminadas só está á disposición dos administradores.",
"undelete-revision": "Revisión eliminada de \"$1\" (o $4 ás $5) feita por $3:",
"undeleterevision-missing": "Revisión non válida ou inexistente. Pode que a ligazón conteña un erro ou que a revisión se restaurase ou eliminase do arquivo.",
+ "undeleterevision-duplicate-revid": "{{PLURAL:$1|Unha revisión non pode ser restaurada|$1 revisións non poden ser restauradas}} porque {{PLURAL:$1|o seu|os seus}} <code>rev_id</code> xa {{PLURAL:$1|está|están}} en uso.",
"undelete-nodiff": "Non se atopou ningunha revisión anterior.",
"undeletebtn": "Restaurar",
"undeletelink": "ver/restaurar",
"undeletedrevisions": "{{PLURAL:$1|Restaurouse $1 revisión|Restauráronse $1 revisións}}",
"undeletedrevisions-files": "Restauráronse $1 {{PLURAL:$1|revisión|revisións}} e $2 {{PLURAL:$2|ficheiro|ficheiros}}",
"undeletedfiles": "{{PLURAL:$1|Restaurouse $1 ficheiro|Restauráronse $1 ficheiros}}",
- "cannotundelete": "Houbo un erro durante a restauración:\n$1",
+ "cannotundelete": "Algunhas ou todas as restauracións fallaronː\n$1",
"undeletedpage": "'''A páxina \"$1\" foi restaurada'''\n\nComprobe o [[Special:Log/delete|rexistro de borrados]] para ver as entradas recentes no rexistro de páxinas eliminadas e restauradas.",
"undelete-header": "Consulte [[Special:Log/delete|no rexistro de borrados]] as páxinas borradas recentemente.",
"undelete-search-title": "Procurar páxinas borradas",
"sp-contributions-newbies-sub": "Contribucións dos usuarios novos",
"sp-contributions-newbies-title": "Contribucións dos usuarios novos",
"sp-contributions-blocklog": "rexistro de bloqueos",
- "sp-contributions-suppresslog": "contribucións borradas do usuario",
- "sp-contributions-deleted": "contribucións borradas do usuario",
+ "sp-contributions-suppresslog": "contribucións {{GENDER:$1|do usuario|da usuaria}} suprimidas",
+ "sp-contributions-deleted": "contribucións {{GENDER:$1|do usuario|da usuaria}} borradas",
"sp-contributions-uploads": "cargas",
"sp-contributions-logs": "rexistros",
"sp-contributions-talk": "conversa",
"mainpage-nstab": "𐌰𐌽𐌰𐍃𐍄𐍉𐌳𐌴𐌹𐌽𐌹𐌻𐌰𐌿𐍆𐍃",
"error": "𐌰𐌹𐍂𐌶𐌴𐌹",
"databaseerror-error": "𐌰𐌹𐍂𐌶𐌴𐌹: $1",
- "missing-article": "ð\90\8d\83ð\90\8c° ð\90\8c³ð\90\8c°ð\90\8d\84ð\90\8c°ð\90\8c±ð\90\8c¿ð\90\8d\83 ð\90\8c½ð\90\8c¹ ð\90\8c²ð\90\8c°ð\90\8c½ð\90\8c°ð\90\8c¼ ð\90\8c¸ð\90\8c°ð\90\8c½ð\90\8c° ð\90\8c±ð\90\8d\89ð\90\8cºð\90\8c°ð\90\8d\85ð\90\8c°ð\90\8c¿ð\90\8d\82ð\90\8c³ð\90\8c°ð\90\8c½ ð\90\8c´ð\90\8c¹ ð\90\8c¹ð\90\8d\84ð\90\8c° ð\90\8d\83ð\90\8cºð\90\8c°ð\90\8c» ð\90\8c±ð\90\8c¹ð\90\8c²ð\90\8c¹ð\90\8d\84ð\90\8c°ð\90\8c½: \"$1\" $2\n\n(The data base did not find the text of a page that it should have found, named \"$1\" $2.\n\nThis is usually caused by following an outdated diff or history link to a page that has been deleted.\n\nIf this is not the case, you may have found a bug in the software.\nPlease report this to an [[Special:ListUsers/sysop|administrator]], making note of the URL.)",
+ "missing-article": "ð\90\8c³ð\90\8c°ð\90\8d\84ð\90\8c°ð\90\8c±ð\90\8c´ð\90\8d\83 ð\90\8c½ð\90\8c¹ ð\90\8c±ð\90\8c¹ð\90\8c²ð\90\8c°ð\90\8d\84 ð\90\8c±ð\90\8d\89ð\90\8cºð\90\8d\89ð\90\8d\83 ð\90\8c»ð\90\8c°ð\90\8c¿ð\90\8c±ð\90\8c¹ð\90\8d\83 ð\90\8c¸ð\90\8c¹ð\90\8c¶ð\90\8c´ð\90\8c¹ ð\90\8d\83ð\90\8cºð\90\8c¿ð\90\8c»ð\90\8c³ð\90\8c´ð\90\8c³ð\90\8c¹ ð\90\8c±ð\90\8c¹ð\90\8c²ð\90\8c¹ð\90\8d\84ð\90\8c°ð\90\8c½, ð\90\8c·ð\90\8c°ð\90\8c¹ð\90\8d\84ð\90\8c°ð\90\8c½ð\90\8d\83 \"$1\" $2. \n\nð\90\8c¸ð\90\8c°ð\90\8d\84ð\90\8c° ð\90\8c¿ð\90\8d\86ð\90\8d\84ð\90\8c° ð\90\8d\85ð\90\8c°ð\90\8c¹ð\90\8d\82ð\90\8c¸ð\90\8c¹ð\90\8c¸ ð\90\8c¾ð\90\8c°ð\90\8c±ð\90\8c°ð\90\8c¹ ð\90\8c»ð\90\8c°ð\90\8c¹ð\90\8d\83ð\90\8d\84ð\90\8c¾ð\90\8c°ð\90\8c³ð\90\8c° ð\90\8d\86ð\90\8c°ð\90\8c¹ð\90\8d\82ð\90\8c½ð\90\8c¾ð\90\8c° ð\90\8c³ð\90\8c¹ð\90\8d\86ð\90\8d\86 ð\90\8c¸ð\90\8c°ð\90\8c¿ ð\90\8d\83ð\90\8d\80ð\90\8c¹ð\90\8c»ð\90\8c»ð\90\8c°ð\90\8c²ð\90\8c°ð\90\8d\85ð\90\8c¹ð\90\8d\83ð\90\8d\83 ð\90\8d\83ð\90\8c´ð\90\8c¹ ð\90\8d\86ð\90\8d\82ð\90\8c°ð\90\8cµð\90\8c¹ð\90\8d\83ð\90\8d\84ð\90\8c¹ð\90\8c³ð\90\8c° ð\90\8c¹ð\90\8d\83ð\90\8d\84. ð\90\8c½ð\90\8c¹ð\90\8c±ð\90\8c°ð\90\8c¹ ð\90\8c¹ð\90\8d\83ð\90\8d\84, ð\90\8c¼ð\90\8c°ð\90\8c·ð\90\8d\84ð\90\8d\83 ð\90\8c¹ð\90\8d\83ð\90\8d\84 ð\90\8c´ð\90\8c¹ ð\90\8c±ð\90\8c¹ð\90\8c²ð\90\8c´ð\90\8d\84ð\90\8c´ð\90\8c¹ð\90\8d\83 ð\90\8c°ð\90\8c¹ð\90\8d\82ð\90\8c¶ð\90\8c´ð\90\8c¹ð\90\8c½ ð\90\8c¹ð\90\8c½ ð\90\8d\83ð\90\8c°ð\90\8c¿ð\90\8d\86ð\90\8d\84ð\90\8d\85ð\90\8c°ð\90\8c¹ð\90\8d\82ð\90\8c°. \n\nð\90\8c±ð\90\8c¹ð\90\8c³ð\90\8c¾ð\90\8c°ð\90\8c¼ ð\90\8c¸ð\90\8c¿ð\90\8cº, ð\90\8c¼ð\90\8c´ð\90\8d\82ð\90\8c´ð\90\8c¹ ð\90\8c¸ð\90\8c°ð\90\8d\84ð\90\8c° ð\90\8c³ð\90\8c¿ [[Special:ListUsers/sysop\n|ð\90\8d\82ð\90\8c´ð\90\8c¹ð\90\8cº]] ð\90\8c²ð\90\8c¹ð\90\8d\86ð\90\8c¿ð\90\8c· ð\90\8c²ð\90\8c°ð\90\8d\85ð\90\8c¹ð\90\8d\83ð\90\8d\83.",
"badtitle": "𐌿𐌽𐍂𐌰𐌹𐌷𐍄𐌰𐍄𐌰 𐌿𐍆𐌰𐍂𐌼𐌴𐌻𐌹",
"badtitletext": "𐍆𐍂𐌰𐌹𐌷𐌰𐌽𐍃 𐌻𐌰𐌿𐍆𐍃 𐍅𐌰𐍃 𐌿𐌽𐌲𐌰𐌼𐌰𐌲𐌰𐌽𐌳𐍃, 𐌻𐌰𐌿𐍃, 𐌰𐌹𐌸𐌸𐌰𐌿 𐌿𐌽𐍂𐌰𐌹𐌷𐍄𐌰𐌱𐌰 𐌲𐌰𐍅𐌹𐌳𐌰𐌽𐍃 𐌼𐌹𐌸𐍂𐌰𐌶𐌳𐌰 𐌸𐌰𐌿 𐌼𐌹𐌸-𐍅𐌹𐌺𐌹 𐌿𐍆𐌰𐍂𐌼𐌴𐌻𐌹. 𐌼𐌰𐌲𐌹 𐌷𐌰𐌱𐌰𐌽 𐌰𐌹𐌽𐌰 𐌸𐌰𐌿 𐌼𐌰𐌽𐌰𐌲𐌹𐌶𐍉𐍃 𐌱𐍉𐌺𐍉𐍃 𐌱𐍂𐌿𐌺𐌹𐌳𐍉𐍃 𐌹𐌽 𐌿𐍆𐌰𐍂𐌼𐌴𐌻𐌾𐌰𐌼.",
"viewsource": "𐍃𐌰𐌹𐍈 𐌱𐍂𐌿𐌽𐌽𐌰𐌽",
"datedefault": "ברירת המחדל",
"prefs-labs": "אפשרויות מעבדה",
"prefs-user-pages": "דפי משתמש",
- "prefs-personal": "פרטי המשתמש",
+ "prefs-personal": "פרטי ה{{GENDER:|משתמש|משתמשת}}",
"prefs-rc": "שינויים אחרונים",
"prefs-watchlist": "רשימת המעקב",
"prefs-editwatchlist": "עריכת רשימת המעקב",
"undeletehistorynoadmin": "Questa pagina è stata cancellata.\nIl motivo della cancellazione è mostrato qui sotto, assieme ai dettagli dell'utente che ha modificato questa pagina prima della cancellazione.\nIl testo contenuto nelle versioni cancellate è disponibile solo agli amministratori.",
"undelete-revision": "Versione cancellata della pagina $1, inserita il $4 alle $5 da $3:",
"undeleterevision-missing": "Versione errata o mancante. Il collegamento è errato oppure la versione è stata già ripristinata o eliminata dall'archivio.",
+ "undeleterevision-duplicate-revid": "{{PLURAL:$1|Una versione non può essere ripristinata|$1 versioni non possono essere ripristinate}}, poiché {{PLURAL:$1|il suo|i loro}} <code>rev_id</code> {{PLURAL:$1|è già utilizzato|sono già utilizzati}}.",
"undelete-nodiff": "Non è stata trovata nessuna versione precedente.",
"undeletebtn": "Ripristina",
"undeletelink": "visualizza/ripristina",
"sp-contributions-username": "IP-мекенжайы немесе қатысушы аты:",
"sp-contributions-toponly": "Өңдемелердің тек соңғы нұсқаларын көрсету",
"sp-contributions-newonly": "Бет бастау өңдемелерін ғана көрсету",
+ "sp-contributions-hideminor": "Шағын өңдемелерді жасыру",
"sp-contributions-submit": "Іздеу",
"whatlinkshere": "Мұнда сілтейтін беттер",
"whatlinkshere-title": "$1 дегенге сілтейтін беттер",
"acct_creation_throttle_hit": "당신의 IP 주소를 이용한 방문자가 이전에 이미 {{PLURAL:$1|계정 $1개}}를 만들어, 계정 만들기 한도를 초과하였습니다.\n따라서 지금은 이 IP 주소로는 더 이상 계정을 만들 수 없습니다.",
"emailauthenticated": "이메일 주소가 $2 $3에 인증되었습니다.",
"emailnotauthenticated": "이메일 주소를 인증하지 않았습니다.\n이메일 확인 절차를 거치지 않으면 다음 이메일 기능을 사용할 수 없습니다.",
- "noemailprefs": "이 기능을 사용하기 위해서는 사용자 환경 설정에서 이메일 주소를 설정해야 합니다.",
+ "noemailprefs": "이 기능을 사용하려면 사용자 환경 설정에서 이메일 주소를 지정하세요.",
"emailconfirmlink": "이메일 주소 확인",
"invalidemailaddress": "이메일 주소의 형식이 잘못되어 인식할 수 없습니다.\n정상적인 형식의 이메일을 입력하거나 칸을 비워 주세요.",
"cannotchangeemail": "이 위키에서는 계정의 이메일 주소를 바꿀 수 없습니다.",
"rev-showdeleted": "보이기",
"revisiondelete": "판 삭제/되살리기",
"revdelete-nooldid-title": "대상 판이 잘못되었습니다.",
- "revdelete-nooldid-text": "이 기능을 수행할 특정 판을 제시하지 않았거나 해당 판이 없습니다. 또는 현재 판을 숨기려 하고 있을 수도 있습니다.",
+ "revdelete-nooldid-text": "이 기능을 수행할 대상 판을 지정하지 않았거나 해당 판이 존재하지 않습니다. 아니면 현재 판을 숨기려 하고 있을 수도 있습니다.",
"revdelete-no-file": "해당 파일이 존재하지 않습니다.",
"revdelete-show-file-confirm": "정말 \"<nowiki>$1</nowiki>\" 파일의 삭제된 $2 $3 버전을 보시겠습니까?",
"revdelete-show-file-submit": "예",
"tags-edit-success": "바뀜이 적용되었습니다.",
"tags-edit-failure": "수정 사항이 적용될 수 없습니다: $1",
"tags-edit-nooldid-title": "대상 판이 잘못되었습니다",
- "tags-edit-nooldid-text": "이 기능을 수행할 특정 판을 제시하지 않았거나 해당 판이 없습니다.",
+ "tags-edit-nooldid-text": "이 기능을 수행할 대상 판을 지정하지 않았거나 해당 판이 존재하지 않습니다.",
"tags-edit-none-selected": "추가하거나 제거할 최소 하나 이상의 태그를 선택하세요.",
"comparepages": "문서 비교",
"compare-page1": "첫 번째 문서",
"passwordreset-emailelement": "सदस्यनाव: \n$1\n\nअस्थायी परवलीचा शब्द: \n$2",
"passwordreset-emailsentemail": "जर हा विपत्रपत्ता आपल्या खात्याशी संलग्न असेल तर, परवलीच्या शब्दाच्या पुनर्स्थापनेबाबत एक विपत्र पाठवण्यात येईल.",
"passwordreset-emailsentusername": "जर या सदस्यनावाशी संलग्न विपत्रपत्ता असेल तर, परवलीचा शब्द पुनर्स्थापनाबाबत विपत्र पाठविल्या जाईल.",
- "passwordreset-emailsent-capture": "'परवलीचा शब्द' पुनर्स्थापनेबाबत एक विपत्र पाठवण्यात आले आहे जे खाली दर्शविण्यात आले आहे.",
- "passwordreset-emailerror-capture": "'परवलीचा शब्द' पुनर्स्थापनेबाबत एक विपत्र निर्माण करण्यात आले, जे खाली दर्शविण्यात आले आहे.परंतु,{{GENDER:$2|सदस्य}}ला पाठविणे असफल झाले: $1",
"changeemail": "विपत्रपत्ता बदला किंवा हटवा",
"changeemail-header": "आपला विपत्रपत्ता बदलण्यास हे आवेदन पूर्ण करा.जर आपणास आपल्या खात्याशी संलग्न कोणताही विपत्रपत्ता हटवायचा असेल तर,आवेदन सादर करण्यापूर्वी, नविन विपत्रपत्त्यासाठी असलेली जागा कोरी ठेवा.",
- "changeemail-passwordrequired": "हे बदल नक्की करण्यासाठी आपणास आपला परवलीचा शब्द टाकावा लागेल.",
"changeemail-no-info": "हे पान थेट बघण्यासठी तुम्हाला सनोंद-प्रवेशित असावे लागेल.",
"changeemail-oldemail": "सध्याचा ईमेल पत्ता :",
"changeemail-newemail": "नवा ईमेल पत्ता:",
"undo-nochange": "असे दिसते कि हे संपादन पूर्ववत केल्या गेले आहे.",
"undo-summary": "[[Special:Contributions/$2|$2]] ([[User talk:$2|चर्चा]])यांची आवृत्ती $1 परतवली.",
"undo-summary-username-hidden": "अज्ञात सदस्याची $1 आवृत्ती परतवा",
- "cantcreateaccounttitle": "खाते उघडू शकत नाही",
"cantcreateaccount-text": "('''$1''')या आंतरजाल अंकपत्त्याकडूनच्या खाते निर्मितीस [[User:$3|$3]]ने अटकाव केला आहे.\n\n$3ने ''$2'' कारण दिले आहे.",
"cantcreateaccount-range-text": "<strong>$1</strong>आवाक्यातील आंतरजाल अंकपत्ते,ज्यात आपल्या (<strong>$4</strong>) या अंकपत्त्याचा समावेश आहे, [[User:$3|$3]] ने त्यांच्या खाते निर्मितीस प्रतिबंध केला आहे.\n\n$3 ने <em>$2</em>कारण दिले आहे.",
"viewpagelogs": "या पानाच्या नोंदी पहा",
"undeletedrevisions": "{{PLURAL:$1|1 आवर्तन|$1 आवर्तने}} पुनर्स्थापित",
"undeletedrevisions-files": "{{PLURAL:$1|1 आवर्तन|$1 आवर्तने}}आणि {{PLURAL:$2|1 संचिका|$2 संचिका}} पुनर्स्थापित",
"undeletedfiles": "{{PLURAL:$1|1 संचिका|$1 संचिका}} पुनर्स्थापित",
- "cannotundelete": "उलटवणे फसले:$1",
+ "cannotundelete": "à¤\95ाहà¥\80 à¤\95िà¤\82वा सरà¥\8dवà¤\9a à¤\89लà¤\9fवणà¥\87 फसलà¥\87:$1",
"undeletedpage": "<strong>$1ला पुनर्स्थापित केले</strong>\n\nअलिकडिल वगळलेल्या आणि पुनर्स्थापितांच्या नोंदीकरिता [[Special:Log/delete|वगळल्याच्या नोंदी]] पहा .",
"undelete-header": "अलीकडील वगळलेल्या पानांकरिता [[Special:Log/delete|वगळलेल्या नोंदी]] पहा.",
"undelete-search-title": "वगळलेली पाने शोधा",
"sp-contributions-newbies-sub": "नवशिक्यांसाठी",
"sp-contributions-newbies-title": "नवीन खात्यांसाठी सदस्य योगदान",
"sp-contributions-blocklog": "रोध नोंदी",
- "sp-contributions-suppresslog": "सदस्य योगदानाचे दमन केले",
- "sp-contributions-deleted": "वगळलेली सदस्य संपादने",
+ "sp-contributions-suppresslog": "{{GENDER:$1|सदस्य}} योगदानाचे दमन केले",
+ "sp-contributions-deleted": "वगळलेली {{GENDER:$1|सदस्य}} संपादने",
"sp-contributions-uploads": "अपभारणे",
"sp-contributions-logs": "नोंदी",
"sp-contributions-talk": "चर्चा",
"action-applychangetags": "appreca tag pe' tramente ca se fanno 'e cagnamiente vuoste",
"action-changetags": "azzecca o lèva tag a caso dint'a verziune nnividuale e riggistre 'e log",
"action-deletechangetags": "scancellare 'e tag d' 'o database",
+ "action-purge": "agghiuorna sta paggena",
"nchanges": "$1 {{PLURAL:$1|cagnamiento|cagnamiente}}",
"enhancedrc-since-last-visit": "$1 {{PLURAL:$1|'a ll'urdema visita}}",
"enhancedrc-history": "cronologgia",
"uploadstash-errclear": "'A pulezzia d' 'e file scassaje.",
"uploadstash-refresh": "Agghiuorna l'elenco d' 'e file",
"uploadstash-thumbnail": "vide miniatura",
+ "uploadstash-exception": "Nun s'è pututo sarvà 'a càrreca dint' 'a stash ($1): \"$2\".",
"invalid-chunk-offset": "Distanza d' 'a parte nun valida",
"img-auth-accessdenied": "Acciesso negato",
"img-auth-nopathinfo": "PATH_INFO mancante.\n'O server nun è mpustato pe' passà sta nfurmazione.\nPuò darse ca, essenno basato ncopp'a CGI, nun putesse suppurtà img_auth.\nVide https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization",
"watchnologin": "Acciesso nun affettuato",
"addwatch": "Miette dint' 'a l'elenco 'e paggene cuntrullate",
"addedwatchtext": "'A paggena \"[[:$1]]\" e 'a paggena 'e chiacchiera è stata azzeccata dint'a l'elenco 'e [[Special:Watchlist|paggene cuntrullate]].",
+ "addedwatchtext-talk": "\"[[:$1]]\" e 'a paggena 'e chiacchiera suòccia è stata azzeccata dint'a l'elenco 'e [[Special:Watchlist|paggene cuntrullate]] vuosto.",
"addedwatchtext-short": "Chista paggena \"$1\" è stata azzeccata a l'elenco 'e paggene cuntrullate.",
"removewatch": "Leva 'a l'elenco 'e paggene cuntrullate",
- "removedwatchtext": "\"[[:$1]]\" 'e 'a paggena 'e chiacchiera soja so' state scancellata 'a l'elenco [[Special:Watchlist|'e paggene cuntrullate]] vuosto.",
+ "removedwatchtext": "\"[[:$1]]\" e 'a paggena 'e chiacchiera soja so' state scancellate 'a l'elenco [[Special:Watchlist|'e paggene cuntrullate]] vuosto.",
+ "removedwatchtext-talk": "\"[[:$1]]\" e 'a paggena 'e chiacchiera soja so' state luvate 'a l'elenco [[Special:Watchlist|'e paggene cuntrullate]] vuosto.",
"removedwatchtext-short": "Chista paggena \"$1\" è stata luvata a l'elenco 'e paggene cuntrullate.",
"watch": "Secuta",
"watchthispage": "Tiene d'uocchio sta paggena",
"grant-group-high-volume": "Realizar actividades em grande quantidade",
"grant-group-customization": "Personalização e preferências",
"grant-group-administration": "Executar acções administrativas",
+ "grant-group-private-information": "Aceder aos seus dados privados",
"grant-group-other": "Actividade diversa",
"grant-blockusers": "Bloquear e desbloquear utilizadores",
"grant-createaccount": "Criar contas",
"grant-group-high-volume": "Izvajanje visokoobsežnih dejavnosti",
"grant-group-customization": "Prilagoditve in nastavitve",
"grant-group-administration": "Izvajanje administrativnih dejanj",
+ "grant-group-private-information": "Dostop do zasebnih podatkov o vas",
"grant-group-other": "Druga dejavnost",
"grant-blockusers": "Blokiranje in odblokiranje uporabnikov",
"grant-createaccount": "Ustvarjanje računov",
"grant-highvolume": "Visokoobsežno urejanje",
"grant-oversight": "Skrivanje uporabnikov in zatiranje redakcij",
"grant-patrol": "Nadzor sprememb strani",
+ "grant-privateinfo": "Dostop do zasebnih podatkov",
"grant-protect": "Zaščita in odstranitev zaščite strani",
"grant-rollback": "Razveljavitev sprememb strani",
"grant-sendemail": "Pošiljanje e-pošte drugim uporabnikom",
"watchthis": "Спостерігати за цією сторінкою",
"savearticle": "Зберегти сторінку",
"savechanges": "Зберегти зміни",
- "publishpage": "Ð\9eпÑ\83блÑ\96кÑ\83вати сторінку",
- "publishchanges": "Ð\9eпÑ\83блÑ\96кÑ\83вати зміни",
+ "publishpage": "Ð\97беÑ\80егти сторінку",
+ "publishchanges": "Ð\97беÑ\80егти зміни",
"preview": "Попередній перегляд",
"showpreview": "Попередній перегляд",
"showdiff": "Показати зміни",
"shortpages": "چھوٹے صفحات",
"longpages": "طویل ترین صفحات",
"deadendpages": "مردہ صفحات",
- "protectedpages": "محفوظ شدہ صفحات",
+ "protectedpages": "محفوظ کردہ صفحات",
"protectedpages-noredirect": "رجوع مکررات چھپائیں",
"protectedpages-timestamp": "وقت کی مہر",
"protectedpages-page": "صفحہ",
"minoredit": "Bu kichik tahrir",
"watchthis": "Sahifani kuzatish",
"savearticle": "Saqla",
+ "publishpage": "Sahifani chop et",
+ "publishchanges": "Oʻzgarishlarni chop et",
"preview": "Ko‘rib chiqish",
"showpreview": "Koʻrib chiqish",
"showdiff": "Kiritilgan o‘zgarishlar",
"undo-success": "Tahrirni bekor qilish imkoniyati bor. Iltimos, solishtirish oynasini koʻrib chiqib, aynan shu oʻzgarishlarni bekor qilmoqchiligingizga ishonch hosil qiling va undan keyin «Saqla» tugmasini bosing.",
"undo-failure": "Keyingi tahrirlar bilan chalkashib ketgani sababli, ushbu tahrirni alohida oʻzini bekor qilishni iloji yoʻq.",
"undo-summary": "[[Special:Contributions/$2|$2]] ([[User talk:$2|mun.]]) tomonidan qilingan $1-sonli tahrir qaytarildi",
- "cantcreateaccounttitle": "Ro‘yxatdan o‘tib bo‘lmadi",
"cantcreateaccount-text": "[[User:$3|$3]] ushbu IP manzil (<strong>$1</strong>) orqali ro‘yxatdan o‘tishni bloklab qo‘ygan.\n\n$3 <em>$2</em>ni sabab qilib ko‘rsatdi",
"cantcreateaccount-range-text": "[[User:$3|$3]] <strong>$1</strong> sohaga tegishli IP manzillar, shu jumladan sizning IP manzilingiz (<strong>$4</strong>), orqali ro‘yxatdan o‘tishni bloklab qo‘ygan.\n\n$3 <em>$2</em>ni sabab qilib ko‘rsatdi",
"viewpagelogs": "Ushbu sahifaga doir qaydlarni koʻrsat",
"revdelete-uname-unhid": "公开用户名",
"revdelete-restricted": "应用对管理员的限制",
"revdelete-unrestricted": "删除对管理员的限制",
- "logentry-block-block": "$1{{GENDER:$2|封禁了}}{{GENDER:$4|$3}},期限为$5 $6",
+ "logentry-block-block": "$1{{GENDER:$2|封禁了}}{{GENDER:$4|$3}},期限至$5 $6",
"logentry-block-unblock": "$1{{GENDER:$2|解封了}}{{GENDER:$4|$3}}",
"logentry-block-reblock": "$1将{{GENDER:$4|$3}}的封禁设置{{GENDER:$2|更改为}}持续时间$5 $6",
"logentry-suppress-block": "$1{{GENDER:$2|封禁了}}{{GENDER:$4|$3}},持续时间$5 $6",
"log-action-filter-upload-upload": "新上传",
"log-action-filter-upload-overwrite": "重新上传",
"authmanager-authn-not-in-progress": "身份验证尚未进行,或会话数据丢失。请从头重新开始。",
- "authmanager-authn-no-primary": "提供的证书不能被验证。",
+ "authmanager-authn-no-primary": "提供的凭据不能通过验证。",
"authmanager-authn-no-local-user": "提供的证书没有与该wiki上的任何用户相关联。",
"authmanager-authn-no-local-user-link": "提供的证书有效,但没有与该wiki上的任何用户相关联。请通过不同方式登录,或创建一个新用户,然后您将拥有一个把您之前的证书链接到对应账户的选项。",
"authmanager-authn-autocreate-failed": "所有账户的自动创建失败:$1",
require_once __DIR__ . '/cleanupTable.inc';
/**
- * Maintenance script to clean up broken page links when somebody turns on $wgCapitalLinks.
+ * Maintenance script to clean up broken page links when somebody turns
+ * on or off $wgCapitalLinks.
*
* @ingroup Maintenance
*/
class CapsCleanup extends TableCleanup {
private $user;
+ private $namespace;
public function __construct() {
parent::__construct();
}
public function execute() {
- global $wgCapitalLinks;
-
- if ( $wgCapitalLinks ) {
- $this->error( "\$wgCapitalLinks is on -- no need for caps links cleanup.", true );
- }
-
$this->user = User::newSystemUser( 'Conversion script', [ 'steal' => true ] );
$this->namespace = intval( $this->getOption( 'namespace', 0 ) );
+
+ if ( MWNamespace::isCapitalized( $this->namespace ) ) {
+ $this->output( "Will be moving pages to first letter capitalized titles" );
+ $callback = 'processRowToUppercase';
+ } else {
+ $this->output( "Will be moving pages to first letter lowercase titles" );
+ $callback = 'processRowToLowercase';
+ }
+
$this->dryrun = $this->hasOption( 'dry-run' );
$this->runTable( [
'table' => 'page',
'conds' => [ 'page_namespace' => $this->namespace ],
'index' => 'page_id',
- 'callback' => 'processRow' ] );
+ 'callback' => $callback ] );
}
- protected function processRow( $row ) {
+ protected function processRowToUppercase( $row ) {
+ global $wgContLang;
+
+ $current = Title::makeTitle( $row->page_namespace, $row->page_title );
+ $display = $current->getPrefixedText();
+ $lower = $row->page_title;
+ $upper = $wgContLang->ucfirst( $row->page_title );
+ if ( $upper == $lower ) {
+ $this->output( "\"$display\" already uppercase.\n" );
+
+ return $this->progress( 0 );
+ }
+
+ $target = Title::makeTitle( $row->page_namespace, $upper );
+ if ( $target->exists() ) {
+ // Prefix "CapsCleanup" to bypass the conflict
+ $target = Title::newFromText( __CLASS__ . '/' . $display );
+ }
+ $ok = $this->movePage(
+ $current,
+ $target,
+ 'Converting page title to first-letter uppercase',
+ false
+ );
+ if ( $ok ) {
+ $this->progress( 1 );
+ if ( $row->page_namespace == $this->namespace ) {
+ $talk = $target->getTalkPage();
+ $row->page_namespace = $talk->getNamespace();
+ if ( $talk->exists() ) {
+ return $this->processRowToUppercase( $row );
+ }
+ }
+ }
+
+ return $this->progress( 0 );
+ }
+
+ protected function processRowToLowercase( $row ) {
global $wgContLang;
$current = Title::makeTitle( $row->page_namespace, $row->page_title );
}
$target = Title::makeTitle( $row->page_namespace, $lower );
- $targetDisplay = $target->getPrefixedText();
if ( $target->exists() ) {
+ $targetDisplay = $target->getPrefixedText();
$this->output( "\"$display\" skipped; \"$targetDisplay\" already exists\n" );
return $this->progress( 0 );
}
- if ( $this->dryrun ) {
- $this->output( "\"$display\" -> \"$targetDisplay\": DRY RUN, NOT MOVED\n" );
- $ok = true;
- } else {
- $mp = new MovePage( $current, $target );
- $status = $mp->move( $this->user, 'Converting page titles to lowercase', true );
- $ok = $status->isOK() ? 'OK' : $status->getWikiText( false, false, 'en' );
- $this->output( "\"$display\" -> \"$targetDisplay\": $ok\n" );
- }
+ $ok = $this->movePage( $current, $target, 'Converting page titles to lowercase', true );
if ( $ok === true ) {
$this->progress( 1 );
if ( $row->page_namespace == $this->namespace ) {
$talk = $target->getTalkPage();
$row->page_namespace = $talk->getNamespace();
if ( $talk->exists() ) {
- return $this->processRow( $row );
+ return $this->processRowToLowercase( $row );
}
}
}
return $this->progress( 0 );
}
+
+ /**
+ * @param Title $current
+ * @param Title $target
+ * @param string $reason
+ * @param bool $createRedirect
+ * @return bool Success
+ */
+ private function movePage( Title $current, Title $target, $reason, $createRedirect ) {
+ $display = $current->getPrefixedText();
+ $targetDisplay = $target->getPrefixedText();
+
+ if ( $this->dryrun ) {
+ $this->output( "\"$display\" -> \"$targetDisplay\": DRY RUN, NOT MOVED\n" );
+ $ok = 'OK';
+ } else {
+ $mp = new MovePage( $current, $target );
+ $status = $mp->move( $this->user, $reason, $createRedirect );
+ $ok = $status->isOK() ? 'OK' : $status->getWikiText( false, false, 'en' );
+ $this->output( "\"$display\" -> \"$targetDisplay\": $ok\n" );
+ }
+
+ return $ok === 'OK';
+ }
}
$maintClass = "CapsCleanup";
'scripts' => 'resources/src/mediawiki/mediawiki.experiments.js',
'targets' => [ 'desktop', 'mobile' ],
],
- 'mediawiki.raggett' => [
- 'styles' => 'resources/src/mediawiki/mediawiki.raggett.css',
- 'targets' => [ 'desktop', 'mobile' ],
- ],
/* MediaWiki Action */
'es5-shim',
'oojs',
'oojs-ui-core.styles',
+ 'oojs-ui.styles.icons',
+ 'oojs-ui.styles.indicators',
+ 'oojs-ui.styles.textures',
'mediawiki.language',
],
'targets' => [ 'desktop', 'mobile' ],
'styles' => 'resources/src/oojs-ui-local.css', // HACK, see inside the file
'skinStyles' => $getSkinSpecific( 'core' ),
'targets' => [ 'desktop', 'mobile' ],
- // ResourceLoaderImageModule doesn't support 'skipFunction', so instead we set this up so that
- // this module is skipped together with its dependencies. Nothing else depends on these modules.
- 'dependencies' => [
- 'oojs-ui.styles.icons',
- 'oojs-ui.styles.indicators',
- 'oojs-ui.styles.textures',
- ],
- 'skipFunction' => 'resources/src/oojs-ui-styles-skip.js',
];
// Additional widgets and layouts module.
--- /dev/null
+These license icons are used in LocalSettings.php files that are generated by
+the installer. Although "Public domain" has been removed from the installer as
+an option, the public-domain.png image needs to remain here to support older
+installations that refer to it in LocalSettings.php.
"Calak",
"Muhammed taha",
"Serwan",
- "Pirehelokan"
+ "Pirehelokan",
+ "Sarchia"
]
},
"ooui-toolbar-more": "زیاتر",
"ooui-dialog-process-dismiss": "لێگەڕان",
"ooui-dialog-process-retry": "دیسان ھەوڵ بدە",
"ooui-dialog-process-continue": "درێژە بدە",
- "ooui-selectfile-placeholder": "ھیچ فایلێک ھەڵنەبژێراوە"
+ "ooui-selectfile-button-select": "پەڕگەیەک دەستنیشان بکە",
+ "ooui-selectfile-placeholder": "ھیچ فایلێک ھەڵنەبژێراوە",
+ "ooui-selectfile-dragdrop-placeholder": "پەڕگەکان بخەرە ئێرە"
}
"Palnatoke",
"Simeondahl",
"Tehnix",
- "Macofe"
+ "Macofe",
+ "Peter Alberti"
]
},
"ooui-outline-control-move-down": "Flyt ned",
"ooui-outline-control-move-up": "Flyt op",
"ooui-toolbar-more": "Mere",
"ooui-toolgroup-expand": "Mere",
+ "ooui-toolgroup-collapse": "Færre",
+ "ooui-dialog-message-accept": "OK",
+ "ooui-dialog-message-reject": "Afbryd",
+ "ooui-dialog-process-error": "Noget gik galt",
+ "ooui-dialog-process-retry": "Prøv igen",
"ooui-dialog-process-continue": "Fortsæt"
}
"Kghbln",
"Marmase",
"Mirzali",
- "Se4598"
+ "Se4598",
+ "Kumkumuk"
]
},
"ooui-outline-control-move-down": "Bendi bere cêr",
"ooui-outline-control-move-up": "Bendi bere cor",
"ooui-outline-control-remove": "Obcey wedare",
- "ooui-toolbar-more": "Zewbi"
+ "ooui-toolbar-more": "Zewbi",
+ "ooui-toolgroup-expand": "Dehana",
+ "ooui-toolgroup-collapse": "Deha tayn",
+ "ooui-dialog-message-accept": "TEMAM",
+ "ooui-dialog-message-reject": "Bıtexelne",
+ "ooui-dialog-process-error": "Tayê çi ğelet şi...",
+ "ooui-dialog-process-dismiss": "Racın",
+ "ooui-dialog-process-retry": "Fına bıcerbın",
+ "ooui-dialog-process-continue": "Dewam ke",
+ "ooui-selectfile-button-select": "Yu dosya weçinê",
+ "ooui-selectfile-not-supported": "Dosya weçinayış desteg nêvine na",
+ "ooui-selectfile-placeholder": "Dosya nêwçineya",
+ "ooui-selectfile-dragdrop-placeholder": "Dosya tiyara ake"
}
/*!
- * OOjs UI v0.17.7
+ * OOjs UI v0.17.8
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-08-03T16:38:22Z
+ * Date: 2016-08-16T21:13:48Z
*/
( function ( OO ) {
/*!
- * OOjs UI v0.17.7
+ * OOjs UI v0.17.8
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-08-03T16:38:27Z
+ * Date: 2016-08-16T21:13:53Z
*/
.oo-ui-element-hidden {
display: none !important;
cursor: pointer;
display: inline-block;
vertical-align: middle;
- font: inherit;
+ font-family: inherit;
+ font-size: inherit;
line-height: normal;
white-space: nowrap;
-webkit-touch-callout: none;
color: #d45353;
}
.oo-ui-fieldLayout-messages .oo-ui-labelWidget {
- padding: 0;
- line-height: 1.875em;
+ padding: 0.1em 0;
+ line-height: 1.5em;
vertical-align: middle;
}
.oo-ui-actionFieldLayout {
}
.oo-ui-menuSelectWidget {
position: absolute;
+ width: 100%;
+ z-index: 4;
background-color: #ffffff;
margin-top: -1px;
border: 1px solid #cccccc;
.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle {
cursor: pointer;
}
-.oo-ui-dropdownWidget > .oo-ui-menuSelectWidget {
- z-index: 1;
- width: 100%;
-}
.oo-ui-dropdownWidget:last-child {
margin-right: 0;
}
max-width: 50em;
margin-right: 0.5em;
}
-.oo-ui-comboBoxInputWidget > .oo-ui-menuSelectWidget {
- z-index: 1;
- width: 100%;
-}
.oo-ui-comboBoxInputWidget.oo-ui-widget-enabled > .oo-ui-indicatorElement-indicator {
cursor: pointer;
}
.oo-ui-checkboxMultioptionWidget .oo-ui-checkboxInputWidget {
margin-right: 0;
}
+.oo-ui-progressBarWidget {
+ max-width: 50em;
+ background-color: #ffffff;
+ border: 1px solid #cccccc;
+ border-radius: 0.25em;
+ overflow: hidden;
+}
+.oo-ui-progressBarWidget-bar {
+ height: 1em;
+ border-right: 1px solid #cccccc;
+ -webkit-transition: width 250ms ease, margin-left 250ms ease;
+ -moz-transition: width 250ms ease, margin-left 250ms ease;
+ transition: width 250ms ease, margin-left 250ms ease;
+ background-color: #cde7f4;
+ background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #eaf4fa), color-stop(100%, #b0d9ee));
+ background-image: -webkit-linear-gradient(top, #eaf4fa 0, #b0d9ee 100%);
+ background-image: -moz-linear-gradient(top, #eaf4fa 0, #b0d9ee 100%);
+ background-image: linear-gradient(to bottom, #eaf4fa 0, #b0d9ee 100%);
+ -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffeaf4fa', endColorstr='#ffb0d9ee' )";
+}
+.oo-ui-progressBarWidget-indeterminate .oo-ui-progressBarWidget-bar {
+ -webkit-animation: oo-ui-progressBarWidget-slide 2s infinite linear;
+ -moz-animation: oo-ui-progressBarWidget-slide 2s infinite linear;
+ animation: oo-ui-progressBarWidget-slide 2s infinite linear;
+ width: 40%;
+ margin-left: -10%;
+ border-left: 1px solid #a6cee1;
+}
+.oo-ui-progressBarWidget.oo-ui-widget-disabled {
+ opacity: 0.6;
+}
+@-webkit-keyframes oo-ui-progressBarWidget-slide {
+ from {
+ margin-left: -40%;
+ }
+ to {
+ margin-left: 100%;
+ }
+}
+@-moz-keyframes oo-ui-progressBarWidget-slide {
+ from {
+ margin-left: -40%;
+ }
+ to {
+ margin-left: 100%;
+ }
+}
+@keyframes oo-ui-progressBarWidget-slide {
+ from {
+ margin-left: -40%;
+ }
+ to {
+ margin-left: 100%;
+ }
+}
/*!
- * OOjs UI v0.17.7
+ * OOjs UI v0.17.8
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-08-03T16:38:27Z
+ * Date: 2016-08-16T21:13:53Z
*/
.oo-ui-element-hidden {
display: none !important;
cursor: pointer;
display: inline-block;
vertical-align: middle;
- font: inherit;
+ font-family: inherit;
+ font-size: inherit;
line-height: normal;
white-space: nowrap;
-webkit-touch-callout: none;
margin-left: 0;
}
.oo-ui-buttonElement.oo-ui-iconElement > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator {
+ margin-right: 0.25em;
margin-left: 0.46875em;
}
.oo-ui-buttonElement-frameless > .oo-ui-buttonElement-button .oo-ui-indicatorElement-indicator {
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
background-color: #d9d9d9;
border-color: #d9d9d9;
- box-shadow: none;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button {
background-color: #999999;
color: #ffffff;
+ border-color: #999999;
+ z-index: 3;
+}
+.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-buttonElement-active > .oo-ui-buttonElement-button:focus {
+ border-color: #347bff;
}
.oo-ui-buttonElement-framed.oo-ui-widget-enabled.oo-ui-flaggedElement-progressive > .oo-ui-buttonElement-button {
color: #347bff;
}
.oo-ui-fieldLayout-messages .oo-ui-labelWidget {
display: table-cell;
- padding: 0;
- line-height: 1.875;
+ padding: 0.1em 0;
+ line-height: 1.5;
vertical-align: middle;
}
.oo-ui-actionFieldLayout {
border-bottom-right-radius: 2px;
border-top-right-radius: 2px;
}
+.oo-ui-buttonGroupWidget.oo-ui-widget-enabled .oo-ui-buttonElement .oo-ui-buttonElement-button:focus {
+ border-color: #347bff;
+ z-index: 3;
+}
.oo-ui-popupWidget {
position: absolute;
/* @noflip */
}
.oo-ui-menuSelectWidget {
position: absolute;
+ width: 100%;
+ z-index: 4;
background-color: #ffffff;
margin-top: -1px;
border: 1px solid #aaaaaa;
position: relative;
width: 100%;
max-width: 50em;
- background-color: #ffffff;
margin-right: 0.5em;
}
.oo-ui-dropdownWidget-handle {
.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle {
cursor: pointer;
}
-.oo-ui-dropdownWidget > .oo-ui-menuSelectWidget {
- z-index: 1;
- width: 100%;
-}
.oo-ui-dropdownWidget:last-child {
margin-right: 0;
}
.oo-ui-dropdownWidget-handle .oo-ui-labelElement-label {
margin: 0 1em;
}
-.oo-ui-dropdownWidget:hover .oo-ui-dropdownWidget-handle {
+.oo-ui-dropdownWidget.oo-ui-widget-enabled .oo-ui-dropdownWidget-handle {
+ background-color: #ffffff;
+ -webkit-transition: border-color 100ms;
+ -moz-transition: border-color 100ms;
+ transition: border-color 100ms;
+}
+.oo-ui-dropdownWidget.oo-ui-widget-enabled:hover .oo-ui-dropdownWidget-handle {
border-color: #aaaaaa;
}
.oo-ui-dropdownWidget.oo-ui-widget-disabled .oo-ui-dropdownWidget-handle {
max-width: 50em;
margin-right: 0.5em;
}
-.oo-ui-comboBoxInputWidget > .oo-ui-menuSelectWidget {
- z-index: 1;
- width: 100%;
-}
.oo-ui-comboBoxInputWidget.oo-ui-widget-enabled > .oo-ui-indicatorElement-indicator {
cursor: pointer;
}
.oo-ui-checkboxMultioptionWidget .oo-ui-checkboxInputWidget {
margin-right: 0;
}
+.oo-ui-progressBarWidget {
+ max-width: 50em;
+ background-color: #ffffff;
+ border: 1px solid #cccccc;
+ border-radius: 2px;
+ overflow: hidden;
+}
+.oo-ui-progressBarWidget-bar {
+ background-color: #dddddd;
+ height: 1em;
+ -webkit-transition: width 200ms, margin-left 200ms;
+ -moz-transition: width 200ms, margin-left 200ms;
+ transition: width 200ms, margin-left 200ms;
+}
+.oo-ui-progressBarWidget-indeterminate .oo-ui-progressBarWidget-bar {
+ -webkit-animation: oo-ui-progressBarWidget-slide 2s infinite linear;
+ -moz-animation: oo-ui-progressBarWidget-slide 2s infinite linear;
+ animation: oo-ui-progressBarWidget-slide 2s infinite linear;
+ width: 40%;
+ margin-left: -10%;
+ border-left-width: 1px;
+}
+.oo-ui-progressBarWidget.oo-ui-widget-disabled {
+ opacity: 0.6;
+}
+@-webkit-keyframes oo-ui-progressBarWidget-slide {
+ from {
+ margin-left: -40%;
+ }
+ to {
+ margin-left: 100%;
+ }
+}
+@-moz-keyframes oo-ui-progressBarWidget-slide {
+ from {
+ margin-left: -40%;
+ }
+ to {
+ margin-left: 100%;
+ }
+}
+@keyframes oo-ui-progressBarWidget-slide {
+ from {
+ margin-left: -40%;
+ }
+ to {
+ margin-left: 100%;
+ }
+}
/*!
- * OOjs UI v0.17.7
+ * OOjs UI v0.17.8
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-08-03T16:38:22Z
+ * Date: 2016-08-16T21:13:48Z
*/
( function ( OO ) {
OO.ui.mixin.ButtonElement.prototype.setActive = function ( value ) {
this.active = !!value;
this.$element.toggleClass( 'oo-ui-buttonElement-active', this.active );
+ this.updateThemeClasses();
return this;
};
return this;
};
+/**
+ * Progress bars visually display the status of an operation, such as a download,
+ * and can be either determinate or indeterminate:
+ *
+ * - **determinate** process bars show the percent of an operation that is complete.
+ *
+ * - **indeterminate** process bars use a visual display of motion to indicate that an operation
+ * is taking place. Because the extent of an indeterminate operation is unknown, the bar does
+ * not use percentages.
+ *
+ * The value of the `progress` configuration determines whether the bar is determinate or indeterminate.
+ *
+ * @example
+ * // Examples of determinate and indeterminate progress bars.
+ * var progressBar1 = new OO.ui.ProgressBarWidget( {
+ * progress: 33
+ * } );
+ * var progressBar2 = new OO.ui.ProgressBarWidget();
+ *
+ * // Create a FieldsetLayout to layout progress bars
+ * var fieldset = new OO.ui.FieldsetLayout;
+ * fieldset.addItems( [
+ * new OO.ui.FieldLayout( progressBar1, {label: 'Determinate', align: 'top'}),
+ * new OO.ui.FieldLayout( progressBar2, {label: 'Indeterminate', align: 'top'})
+ * ] );
+ * $( 'body' ).append( fieldset.$element );
+ *
+ * @class
+ * @extends OO.ui.Widget
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {number|boolean} [progress=false] The type of progress bar (determinate or indeterminate).
+ * To create a determinate progress bar, specify a number that reflects the initial percent complete.
+ * By default, the progress bar is indeterminate.
+ */
+OO.ui.ProgressBarWidget = function OoUiProgressBarWidget( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.ProgressBarWidget.parent.call( this, config );
+
+ // Properties
+ this.$bar = $( '<div>' );
+ this.progress = null;
+
+ // Initialization
+ this.setProgress( config.progress !== undefined ? config.progress : false );
+ this.$bar.addClass( 'oo-ui-progressBarWidget-bar' );
+ this.$element
+ .attr( {
+ role: 'progressbar',
+ 'aria-valuemin': 0,
+ 'aria-valuemax': 100
+ } )
+ .addClass( 'oo-ui-progressBarWidget' )
+ .append( this.$bar );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.ProgressBarWidget, OO.ui.Widget );
+
+/* Static Properties */
+
+OO.ui.ProgressBarWidget.static.tagName = 'div';
+
+/* Methods */
+
+/**
+ * Get the percent of the progress that has been completed. Indeterminate progresses will return `false`.
+ *
+ * @return {number|boolean} Progress percent
+ */
+OO.ui.ProgressBarWidget.prototype.getProgress = function () {
+ return this.progress;
+};
+
+/**
+ * Set the percent of the process completed or `false` for an indeterminate process.
+ *
+ * @param {number|boolean} progress Progress percent or `false` for indeterminate
+ */
+OO.ui.ProgressBarWidget.prototype.setProgress = function ( progress ) {
+ this.progress = progress;
+
+ if ( progress !== false ) {
+ this.$bar.css( 'width', this.progress + '%' );
+ this.$element.attr( 'aria-valuenow', this.progress );
+ } else {
+ this.$bar.css( 'width', '' );
+ this.$element.removeAttr( 'aria-valuenow' );
+ }
+ this.$element.toggleClass( 'oo-ui-progressBarWidget-indeterminate', progress === false );
+};
+
/**
* InputWidget is the base class for all input widgets, which
* include {@link OO.ui.TextInputWidget text inputs}, {@link OO.ui.CheckboxInputWidget checkbox inputs},
// Events
this.$input.on( {
keypress: this.onKeyPress.bind( this ),
- blur: this.onBlur.bind( this )
+ blur: this.onBlur.bind( this ),
+ focus: this.onFocus.bind( this )
} );
this.$input.one( {
focus: this.onElementAttach.bind( this )
change: 'onChange',
disable: 'onDisable'
} );
+ this.on( 'change', OO.ui.debounce( this.onDebouncedChange.bind( this ), 250 ) );
// Initialization
this.$element
this.setValidityFlag();
};
+/**
+ * Handle focus events.
+ *
+ * @private
+ * @param {jQuery.Event} e Focus event
+ */
+OO.ui.TextInputWidget.prototype.onFocus = function () {
+ this.setValidityFlag( true );
+};
+
/**
* Handle element attach events.
*
*/
OO.ui.TextInputWidget.prototype.onChange = function () {
this.updateSearchIndicator();
- this.setValidityFlag();
this.adjustSize();
};
+/**
+ * Handle debounced change events.
+ *
+ * @param {string} value
+ * @private
+ */
+OO.ui.TextInputWidget.prototype.onDebouncedChange = function () {
+ this.setValidityFlag();
+};
+
/**
* Handle disable events.
*
/*!
- * OOjs UI v0.17.7
+ * OOjs UI v0.17.8
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-08-03T16:38:22Z
+ * Date: 2016-08-16T21:13:48Z
*/
( function ( OO ) {
if ( element.supports( [ 'hasFlag' ] ) ) {
isFramed = element.supports( [ 'isFramed' ] ) && element.isFramed();
isActive = element.supports( [ 'isActive' ] ) && element.isActive();
- if (
- ( isFramed && ( isActive || element.isDisabled() || element.hasFlag( 'primary' ) ) )
- ) {
+ if ( isFramed && ( isActive || element.isDisabled() || element.hasFlag( 'primary' ) ) ) {
// Button with a dark background, use white icon
variants.invert = true;
} else if ( !isFramed && element.isDisabled() ) {
/*!
- * OOjs UI v0.17.7
+ * OOjs UI v0.17.8
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-08-03T16:38:27Z
+ * Date: 2016-08-16T21:13:53Z
*/
.oo-ui-popupTool .oo-ui-popupWidget-popup,
.oo-ui-popupTool .oo-ui-popupWidget-anchor {
/*!
- * OOjs UI v0.17.7
+ * OOjs UI v0.17.8
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-08-03T16:38:27Z
+ * Date: 2016-08-16T21:13:53Z
*/
.oo-ui-popupTool .oo-ui-popupWidget-popup,
.oo-ui-popupTool .oo-ui-popupWidget-anchor {
line-height: 2.1;
padding: 0 0.4em;
}
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool > .oo-ui-tool-link .oo-ui-tool-title {
+ color: #555555;
+}
.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled:hover {
background-color: #eeeeee;
}
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool > a.oo-ui-tool-link .oo-ui-tool-title {
- color: #555555;
-}
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-tool-active.oo-ui-widget-enabled {
- box-shadow: inset 0 0.07em 0.07em 0 rgba(0, 0, 0, 0.07);
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled.oo-ui-tool-active {
background-color: #e5e5e5;
+ box-shadow: inset 0 0.07em 0.07em 0 rgba(0, 0, 0, 0.07);
}
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-tool-active.oo-ui-widget-enabled:hover {
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled.oo-ui-tool-active:hover {
background-color: #eeeeee;
}
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link .oo-ui-tool-title {
- color: #cccccc;
-}
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link .oo-ui-iconElement-icon {
- opacity: 0.2;
+.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled.oo-ui-tool-active:active {
+ background-color: #e5e5e5;
}
.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled > .oo-ui-tool-link .oo-ui-iconElement-icon {
opacity: 0.7;
.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled:hover > .oo-ui-tool-link .oo-ui-iconElement-icon {
opacity: 0.9;
}
-.oo-ui-barToolGroup.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool.oo-ui-widget-enabled:active {
- background-color: #e7e7e7;
-}
-.oo-ui-barToolGroup.oo-ui-widget-disabled > .oo-ui-toolGroup-tools > .oo-ui-tool > a.oo-ui-tool-link .oo-ui-tool-title {
+.oo-ui-barToolGroup.oo-ui-widget-enabled .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link .oo-ui-tool-title,
+.oo-ui-barToolGroup.oo-ui-widget-disabled .oo-ui-tool > .oo-ui-tool-link .oo-ui-tool-title {
color: #cccccc;
}
-.oo-ui-barToolGroup.oo-ui-widget-disabled > .oo-ui-toolGroup-tools > .oo-ui-tool > a.oo-ui-tool-link .oo-ui-iconElement-icon {
+.oo-ui-barToolGroup.oo-ui-widget-enabled .oo-ui-tool.oo-ui-widget-disabled > .oo-ui-tool-link .oo-ui-iconElement-icon,
+.oo-ui-barToolGroup.oo-ui-widget-disabled .oo-ui-tool > .oo-ui-tool-link .oo-ui-iconElement-icon {
opacity: 0.2;
}
.oo-ui-popupToolGroup {
.oo-ui-toolbar-narrow .oo-ui-popupToolGroup.oo-ui-labelElement.oo-ui-indicatorElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
margin-right: 1.75em;
}
-.oo-ui-popupToolGroup.oo-ui-widget-enabled .oo-ui-popupToolGroup-handle:hover {
- background-color: #eeeeee;
-}
-.oo-ui-popupToolGroup.oo-ui-widget-enabled .oo-ui-popupToolGroup-handle:active {
- background-color: #e5e5e5;
-}
.oo-ui-popupToolGroup-handle {
padding: 0.3125em;
height: 2.5em;
.oo-ui-toolbar-narrow .oo-ui-popupToolGroup-handle .oo-ui-iconElement-icon {
left: 0;
}
+.oo-ui-popupToolGroup.oo-ui-widget-enabled .oo-ui-popupToolGroup-handle:hover {
+ background-color: #eeeeee;
+}
+.oo-ui-popupToolGroup.oo-ui-widget-enabled .oo-ui-popupToolGroup-handle:active {
+ background-color: #e5e5e5;
+}
.oo-ui-popupToolGroup-header {
line-height: 2.6;
margin: 0 0.6em;
.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-enabled:hover {
background-color: #eeeeee;
}
-.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-enabled:active {
- background-color: #e7e7e7;
-}
.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-enabled:hover .oo-ui-tool-link .oo-ui-iconElement-icon {
opacity: 0.9;
}
.oo-ui-listToolGroup .oo-ui-tool-active.oo-ui-widget-enabled:hover {
background-color: #eeeeee;
}
-.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-tool-title {
- color: #cccccc;
-}
-.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-tool-accel {
- color: #dddddd;
-}
-.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-iconElement-icon {
- opacity: 0.2;
-}
-.oo-ui-listToolGroup.oo-ui-widget-disabled {
+.oo-ui-listToolGroup.oo-ui-widget-disabled,
+.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-title,
+.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-accel {
color: #cccccc;
}
.oo-ui-listToolGroup.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator,
-.oo-ui-listToolGroup.oo-ui-widget-disabled .oo-ui-iconElement-icon {
+.oo-ui-listToolGroup.oo-ui-widget-disabled .oo-ui-iconElement-icon,
+.oo-ui-listToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-iconElement-icon {
opacity: 0.2;
}
.oo-ui-menuToolGroup .oo-ui-tool {
.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-enabled:hover {
background-color: #eeeeee;
}
-.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-tool-title {
- color: #cccccc;
-}
-.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-link .oo-ui-iconElement-icon {
- opacity: 0.2;
-}
-.oo-ui-menuToolGroup.oo-ui-widget-disabled {
+.oo-ui-menuToolGroup.oo-ui-widget-disabled,
+.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-tool-title {
color: #cccccc;
}
.oo-ui-menuToolGroup.oo-ui-widget-disabled .oo-ui-indicatorElement-indicator,
-.oo-ui-menuToolGroup.oo-ui-widget-disabled .oo-ui-iconElement-icon {
+.oo-ui-menuToolGroup.oo-ui-widget-disabled .oo-ui-iconElement-icon,
+.oo-ui-menuToolGroup .oo-ui-tool.oo-ui-widget-disabled .oo-ui-iconElement-icon {
opacity: 0.2;
}
.oo-ui-toolbar {
/*!
- * OOjs UI v0.17.7
+ * OOjs UI v0.17.8
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-08-03T16:38:22Z
+ * Date: 2016-08-16T21:13:48Z
*/
( function ( OO ) {
/*!
- * OOjs UI v0.17.7
+ * OOjs UI v0.17.8
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-08-03T16:38:27Z
+ * Date: 2016-08-16T21:13:53Z
*/
.oo-ui-draggableElement-handle,
.oo-ui-draggableElement-handle.oo-ui-widget {
.oo-ui-draggableGroupElement-horizontal .oo-ui-draggableElement {
display: inline-block;
}
-.oo-ui-lookupElement > .oo-ui-menuSelectWidget {
- z-index: 1;
- width: 100%;
-}
.oo-ui-bookletLayout-stackLayout.oo-ui-stackLayout-continuous > .oo-ui-panelLayout-scrollable {
overflow-y: hidden;
}
padding: 0;
background-color: transparent;
}
-.oo-ui-buttonOptionWidget .oo-ui-buttonElement-button {
- position: relative;
-}
.oo-ui-buttonOptionWidget.oo-ui-iconElement .oo-ui-iconElement-icon,
.oo-ui-buttonOptionWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
position: static;
left: 2.25em;
margin-left: -2px;
}
-.oo-ui-progressBarWidget {
- max-width: 50em;
- background-color: #ffffff;
- border: 1px solid #cccccc;
- border-radius: 0.25em;
- overflow: hidden;
-}
-.oo-ui-progressBarWidget-bar {
- height: 1em;
- border-right: 1px solid #cccccc;
- -webkit-transition: width 250ms ease, margin-left 250ms ease;
- -moz-transition: width 250ms ease, margin-left 250ms ease;
- transition: width 250ms ease, margin-left 250ms ease;
- background-color: #cde7f4;
- background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0, #eaf4fa), color-stop(100%, #b0d9ee));
- background-image: -webkit-linear-gradient(top, #eaf4fa 0, #b0d9ee 100%);
- background-image: -moz-linear-gradient(top, #eaf4fa 0, #b0d9ee 100%);
- background-image: linear-gradient(to bottom, #eaf4fa 0, #b0d9ee 100%);
- -ms-filter: "progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffeaf4fa', endColorstr='#ffb0d9ee' )";
-}
-.oo-ui-progressBarWidget-indeterminate .oo-ui-progressBarWidget-bar {
- -webkit-animation: oo-ui-progressBarWidget-slide 2s infinite linear;
- -moz-animation: oo-ui-progressBarWidget-slide 2s infinite linear;
- animation: oo-ui-progressBarWidget-slide 2s infinite linear;
- width: 40%;
- margin-left: -10%;
- border-left: 1px solid #a6cee1;
-}
-.oo-ui-progressBarWidget.oo-ui-widget-disabled {
- opacity: 0.6;
-}
-@-webkit-keyframes oo-ui-progressBarWidget-slide {
- from {
- margin-left: -40%;
- }
- to {
- margin-left: 100%;
- }
-}
-@-moz-keyframes oo-ui-progressBarWidget-slide {
- from {
- margin-left: -40%;
- }
- to {
- margin-left: 100%;
- }
-}
-@keyframes oo-ui-progressBarWidget-slide {
- from {
- margin-left: -40%;
- }
- to {
- margin-left: 100%;
- }
-}
.oo-ui-selectFileWidget {
display: inline-block;
vertical-align: middle;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label {
- position: absolute;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- text-overflow: ellipsis;
-}
-.oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-fileType {
- float: right;
-}
.oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator,
-.oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
+.oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon {
position: absolute;
}
.oo-ui-widget-disabled .oo-ui-selectFileWidget-info {
-ms-user-select: none;
user-select: none;
}
-.oo-ui-selectFileWidget .oo-ui-selectFileWidget-clearButton {
+.oo-ui-selectFileWidget-label {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ text-overflow: ellipsis;
+}
+.oo-ui-selectFileWidget-fileType {
+ display: none;
+}
+.oo-ui-selectFileWidget-clearButton {
+ position: absolute;
z-index: 2;
}
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget {
+.oo-ui-selectFileWidget-dropTarget {
+ position: relative;
cursor: default;
- height: 5.5em;
- padding: 0;
+ height: 8.815em;
}
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-dropLabel,
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-selectButton {
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-dropLabel,
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-selectButton,
+.oo-ui-selectFileWidget-dropTarget .oo-ui-iconElement-icon {
display: none;
}
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail {
- height: 5.5em;
- width: 5.5em;
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail {
+ width: 7.815em;
position: absolute;
- background-size: cover;
+ top: 0.5em;
+ bottom: 0.5em;
+ left: 0.5em;
background-position: center center;
+ background-repeat: no-repeat;
+ background-size: contain;
}
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail.oo-ui-pendingElement-pending {
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail.oo-ui-pendingElement-pending {
+ background-repeat: repeat;
background-size: auto;
}
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail > .oo-ui-selectFileWidget-noThumbnail-icon {
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail > .oo-ui-selectFileWidget-noThumbnail-icon {
opacity: 0.4;
- background-color: #cccccc;
- height: 5.5em;
- width: 5.5em;
+ height: 7.815em;
+ width: 7.815em;
}
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info {
- border: 0;
- background: none;
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info {
display: block;
height: 100%;
width: auto;
- margin-left: 5.5em;
+ margin-left: 8.815em;
+ border: 0;
}
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label {
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-label {
+ display: block;
position: relative;
+ top: inherit;
}
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-fileName {
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-fileName {
display: block;
+ padding-top: 0.5em;
padding-right: 2.375em;
- overflow: hidden;
- text-overflow: ellipsis;
}
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-fileType {
- display: block;
- float: none;
-}
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
- position: absolute;
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-clearButton {
right: 0.5em;
}
.oo-ui-selectFileWidget-empty.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail,
}
.oo-ui-selectFileWidget-empty.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-selectButton {
display: block;
- margin: 0.7em;
+ margin: 2.2em 1em 1em;
}
.oo-ui-selectFileWidget-empty.oo-ui-selectFileWidget-dropTarget,
.oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget {
.oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget .oo-ui-buttonElement-button {
cursor: no-drop;
}
+.oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget {
+ height: auto;
+}
+.oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-label {
+ padding: 1em;
+}
.oo-ui-selectFileWidget:last-child {
margin-right: 0;
}
border-radius: 0.25em 0 0 0.25em;
border-width: 1px 0 1px 1px;
}
-.oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
- top: 0;
- right: 0;
- height: 2.3em;
- margin-right: 0.775em;
-}
.oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon {
top: 0;
left: 0;
height: 2.3em;
margin-left: 0.3em;
}
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label {
+.oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
+ top: 0;
+ right: 0;
+ height: 2.3em;
+ margin-right: 0.775em;
+}
+.oo-ui-selectFileWidget-label {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ left: 0.5em;
+ right: 2.175em;
line-height: 2.3em;
margin: 0;
overflow: hidden;
white-space: nowrap;
- -webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
text-overflow: ellipsis;
- left: 0.5em;
- right: 0.5em;
}
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label > .oo-ui-selectFileWidget-fileType {
+.oo-ui-selectFileWidget-fileType {
color: #888888;
+ display: block;
+ margin-top: 0.25em;
}
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
+.oo-ui-selectFileWidget-clearButton {
top: 0;
+ right: 0;
width: 1.875em;
margin-right: 0;
}
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
+.oo-ui-selectFileWidget-clearButton .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
height: 2.3em;
}
-.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info {
- color: #cccccc;
- text-shadow: 0 1px 1px #ffffff;
- border-color: #dddddd;
- background-color: #f3f3f3;
-}
-.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
-.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
- opacity: 0.2;
-}
.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-label {
color: #cccccc;
}
-.oo-ui-selectFileWidget.oo-ui-iconElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
+.oo-ui-selectFileWidget.oo-ui-iconElement .oo-ui-selectFileWidget-label {
left: 2.475em;
}
-.oo-ui-selectFileWidget .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
- right: 2.175em;
-}
-.oo-ui-selectFileWidget .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-clearButton {
- right: 0;
-}
-.oo-ui-selectFileWidget.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
+.oo-ui-selectFileWidget.oo-ui-indicatorElement .oo-ui-selectFileWidget-label {
right: 4.2625em;
}
-.oo-ui-selectFileWidget.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-clearButton {
+.oo-ui-selectFileWidget.oo-ui-indicatorElement .oo-ui-selectFileWidget-clearButton {
right: 2.0875em;
}
-.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label,
-.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
+.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-label,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-label {
right: 0.5em;
}
-.oo-ui-selectFileWidget-empty.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label,
-.oo-ui-selectFileWidget-notsupported.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
+.oo-ui-selectFileWidget-empty.oo-ui-indicatorElement .oo-ui-selectFileWidget-label,
+.oo-ui-selectFileWidget-notsupported.oo-ui-indicatorElement .oo-ui-selectFileWidget-label,
+.oo-ui-selectFileWidget-empty.oo-ui-indicatorElement .oo-ui-selectFileWidget-label,
+.oo-ui-selectFileWidget-notsupported.oo-ui-indicatorElement .oo-ui-selectFileWidget-label {
right: 2em;
}
.oo-ui-selectFileWidget-supported.oo-ui-widget-enabled.oo-ui-selectFileWidget-canDrop.oo-ui-selectFileWidget-dropTarget {
background-color: #e1f3ff;
}
-.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled.oo-ui-selectFileWidget-dropTarget,
-.oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget {
- color: #cccccc;
- text-shadow: 0 1px 1px #ffffff;
- border-color: #dddddd;
- background-color: #f3f3f3;
-}
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget {
+.oo-ui-selectFileWidget-dropTarget {
background-color: #ffffff;
border: 1px solid #aaaaaa;
- margin-bottom: 0.5em;
vertical-align: middle;
border-radius: 0.25em;
}
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button {
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button {
border-radius: 0.25em;
}
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-label {
+ line-height: 1.4;
+ overflow: inherit;
+ white-space: normal;
+}
.oo-ui-selectFileWidget-empty.oo-ui-selectFileWidget-dropTarget {
border-style: dashed;
}
+.oo-ui-selectFileWidget.oo-ui-widget-disabled.oo-ui-selectFileWidget-dropTarget,
+.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled.oo-ui-selectFileWidget-dropTarget,
+.oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget {
+ background-color: #f3f3f3;
+ color: #cccccc;
+ border-color: #dddddd;
+ text-shadow: 0 1px 1px #ffffff;
+}
+.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info,
+.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-info,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info {
+ background-color: #f3f3f3;
+ color: #cccccc;
+ border-color: #dddddd;
+ text-shadow: 0 1px 1px #ffffff;
+}
+.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
+.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
+.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator,
+.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
+ opacity: 0.2;
+}
.oo-ui-outlineOptionWidget {
position: relative;
cursor: pointer;
.oo-ui-capsuleMultiselectWidget-group {
display: inline;
}
-.oo-ui-capsuleMultiselectWidget > .oo-ui-menuSelectWidget {
- z-index: 1;
- width: 100%;
-}
.oo-ui-capsuleMultiselectWidget-handle {
background-color: #ffffff;
cursor: text;
/*!
- * OOjs UI v0.17.7
+ * OOjs UI v0.17.8
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-08-03T16:38:27Z
+ * Date: 2016-08-16T21:13:53Z
*/
.oo-ui-draggableElement-handle,
.oo-ui-draggableElement-handle.oo-ui-widget {
.oo-ui-draggableGroupElement-horizontal .oo-ui-draggableElement {
display: inline-block;
}
-.oo-ui-lookupElement > .oo-ui-menuSelectWidget {
- z-index: 1;
- width: 100%;
-}
.oo-ui-bookletLayout-stackLayout.oo-ui-stackLayout-continuous > .oo-ui-panelLayout-scrollable {
overflow-y: hidden;
}
.oo-ui-buttonSelectWidget:focus {
outline: 0;
}
-.oo-ui-buttonSelectWidget:focus .oo-ui-buttonOptionWidget.oo-ui-optionWidget-selected .oo-ui-buttonElement-button {
- border-color: #347bff;
- box-shadow: inset 0 0 0 1px #347bff;
- z-index: 2;
-}
.oo-ui-buttonSelectWidget .oo-ui-buttonOptionWidget .oo-ui-buttonElement-button {
border-radius: 0;
margin-left: -1px;
border-bottom-right-radius: 2px;
border-top-right-radius: 2px;
}
+.oo-ui-buttonSelectWidget.oo-ui-widget-enabled:focus .oo-ui-buttonOptionWidget.oo-ui-optionWidget-selected .oo-ui-buttonElement-button {
+ border-color: #347bff;
+ box-shadow: inset 0 0 0 1px #347bff;
+}
.oo-ui-buttonOptionWidget {
display: inline-block;
padding: 0;
}
-.oo-ui-buttonOptionWidget .oo-ui-buttonElement-button {
- position: relative;
-}
.oo-ui-buttonOptionWidget.oo-ui-iconElement .oo-ui-iconElement-icon,
.oo-ui-buttonOptionWidget.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
position: static;
-moz-transform: translateZ(0);
-ms-transform: translateZ(0);
transform: translateZ(0);
- height: 2em;
width: 3.5em;
+ min-height: 26px;
+ height: 2em;
border: 1px solid #767676;
border-radius: 1em;
background-color: #ffffff;
transition: border-color 100ms;
}
.oo-ui-toggleSwitchWidget-grip {
- top: 0.35em;
+ top: 0.3125em;
min-width: 16px;
- width: 1.2em;
+ width: 1.25em;
min-height: 16px;
- height: 1.2em;
- border-radius: 1.2em;
+ height: 1.25em;
+ border-radius: 1.25em;
-webkit-transition: left 100ms, margin-left 100ms;
-moz-transition: left 100ms, margin-left 100ms;
transition: left 100ms, margin-left 100ms;
.oo-ui-toggleSwitchWidget.oo-ui-widget-disabled .oo-ui-toggleSwitchWidget-grip {
background-color: #ffffff;
}
-.oo-ui-progressBarWidget {
- max-width: 50em;
- background-color: #ffffff;
- border: 1px solid #cccccc;
- border-radius: 2px;
- overflow: hidden;
-}
-.oo-ui-progressBarWidget-bar {
- background-color: #dddddd;
- height: 1em;
- -webkit-transition: width 200ms, margin-left 200ms;
- -moz-transition: width 200ms, margin-left 200ms;
- transition: width 200ms, margin-left 200ms;
-}
-.oo-ui-progressBarWidget-indeterminate .oo-ui-progressBarWidget-bar {
- -webkit-animation: oo-ui-progressBarWidget-slide 2s infinite linear;
- -moz-animation: oo-ui-progressBarWidget-slide 2s infinite linear;
- animation: oo-ui-progressBarWidget-slide 2s infinite linear;
- width: 40%;
- margin-left: -10%;
- border-left-width: 1px;
-}
-.oo-ui-progressBarWidget.oo-ui-widget-disabled {
- opacity: 0.6;
-}
-@-webkit-keyframes oo-ui-progressBarWidget-slide {
- from {
- margin-left: -40%;
- }
- to {
- margin-left: 100%;
- }
-}
-@-moz-keyframes oo-ui-progressBarWidget-slide {
- from {
- margin-left: -40%;
- }
- to {
- margin-left: 100%;
- }
-}
-@keyframes oo-ui-progressBarWidget-slide {
- from {
- margin-left: -40%;
- }
- to {
- margin-left: 100%;
- }
-}
.oo-ui-selectFileWidget {
display: inline-block;
vertical-align: middle;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label {
- position: absolute;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- text-overflow: ellipsis;
-}
-.oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-fileType {
- float: right;
-}
.oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator,
-.oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
+.oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon {
position: absolute;
}
.oo-ui-widget-disabled .oo-ui-selectFileWidget-info {
-ms-user-select: none;
user-select: none;
}
-.oo-ui-selectFileWidget .oo-ui-selectFileWidget-clearButton {
+.oo-ui-selectFileWidget-label {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ text-overflow: ellipsis;
+}
+.oo-ui-selectFileWidget-fileType {
+ display: none;
+}
+.oo-ui-selectFileWidget-clearButton {
+ position: absolute;
z-index: 2;
}
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget {
+.oo-ui-selectFileWidget-dropTarget {
+ position: relative;
cursor: default;
- height: 5.5em;
- padding: 0;
+ height: 8.815em;
}
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-dropLabel,
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-selectButton {
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-dropLabel,
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-selectButton,
+.oo-ui-selectFileWidget-dropTarget .oo-ui-iconElement-icon {
display: none;
}
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail {
- height: 5.5em;
- width: 5.5em;
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail {
+ width: 7.815em;
position: absolute;
- background-size: cover;
+ top: 0.5em;
+ bottom: 0.5em;
+ left: 0.5em;
background-position: center center;
+ background-repeat: no-repeat;
+ background-size: contain;
}
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail.oo-ui-pendingElement-pending {
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail.oo-ui-pendingElement-pending {
+ background-repeat: repeat;
background-size: auto;
}
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail > .oo-ui-selectFileWidget-noThumbnail-icon {
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail > .oo-ui-selectFileWidget-noThumbnail-icon {
opacity: 0.4;
- background-color: #cccccc;
- height: 5.5em;
- width: 5.5em;
+ height: 7.815em;
+ width: 7.815em;
}
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info {
- border: 0;
- background: none;
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info {
display: block;
height: 100%;
width: auto;
- margin-left: 5.5em;
+ margin-left: 8.815em;
+ border: 0;
}
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label {
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-label {
+ display: block;
position: relative;
+ top: inherit;
}
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-fileName {
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-fileName {
display: block;
+ padding-top: 0.5em;
padding-right: 2.375em;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-fileType {
- display: block;
- float: none;
}
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
- position: absolute;
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-clearButton {
right: 0.5em;
}
.oo-ui-selectFileWidget-empty.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-thumbnail,
}
.oo-ui-selectFileWidget-empty.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-selectButton {
display: block;
- margin: 0.7em;
+ margin: 2.2em 1em 1em;
}
.oo-ui-selectFileWidget-empty.oo-ui-selectFileWidget-dropTarget,
.oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget {
.oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget .oo-ui-buttonElement-button {
cursor: no-drop;
}
+.oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget {
+ height: auto;
+}
+.oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-label {
+ padding: 1em;
+}
.oo-ui-selectFileWidget:last-child {
margin-right: 0;
}
height: 2.3em;
margin-left: 0.5em;
}
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label {
+.oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
+ top: 0;
+ right: 0;
+ height: 2.3em;
+ margin-right: 0.775em;
+}
+.oo-ui-selectFileWidget-label {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
display: block;
+ right: 2.375em;
line-height: 2.3;
margin: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
- left: 0;
- right: 0;
padding-left: 0.5em;
}
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-label > .oo-ui-selectFileWidget-fileType {
+.oo-ui-selectFileWidget-fileType {
color: #888888;
+ display: block;
+ margin-top: 0.25em;
}
-.oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
+.oo-ui-selectFileWidget-clearButton {
top: 0;
right: 0;
- height: 2.3em;
- margin-right: 0.775em;
-}
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton {
- top: 0;
min-width: 24px;
width: 1.875em;
margin-right: 0;
}
-.oo-ui-selectFileWidget-info > .oo-ui-selectFileWidget-clearButton .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
+.oo-ui-selectFileWidget-clearButton .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
height: 2.3em;
}
-.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info,
-.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info {
- background-color: #f3f3f3;
- color: #cccccc;
- text-shadow: 0 1px 1px #ffffff;
- border-color: #dddddd;
- cursor: default;
-}
-.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
-.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
-.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator,
-.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
- opacity: 0.2;
-}
.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-label {
color: #cccccc;
}
-.oo-ui-selectFileWidget.oo-ui-iconElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
+.oo-ui-selectFileWidget.oo-ui-iconElement .oo-ui-selectFileWidget-label {
left: 2.875em;
}
-.oo-ui-selectFileWidget .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
- right: 2.375em;
-}
-.oo-ui-selectFileWidget .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-clearButton {
- right: 0;
-}
-.oo-ui-selectFileWidget.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
+.oo-ui-selectFileWidget.oo-ui-indicatorElement .oo-ui-selectFileWidget-label {
right: 4.4625em;
padding-left: 0;
}
-.oo-ui-selectFileWidget.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-clearButton {
+.oo-ui-selectFileWidget.oo-ui-indicatorElement .oo-ui-selectFileWidget-clearButton {
right: 2.0875em;
}
-.oo-ui-selectFileWidget-empty .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label,
-.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
- right: 0.5em;
-}
-.oo-ui-selectFileWidget-empty.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label,
-.oo-ui-selectFileWidget-notsupported.oo-ui-indicatorElement .oo-ui-selectFileWidget-info .oo-ui-selectFileWidget-label {
+.oo-ui-selectFileWidget-empty.oo-ui-indicatorElement .oo-ui-selectFileWidget-label,
+.oo-ui-selectFileWidget-notsupported.oo-ui-indicatorElement .oo-ui-selectFileWidget-label {
right: 2em;
- padding-left: 0;
}
.oo-ui-selectFileWidget-supported.oo-ui-widget-enabled.oo-ui-selectFileWidget-canDrop.oo-ui-selectFileWidget-dropTarget {
background-color: #ebf2ff;
}
-.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled.oo-ui-selectFileWidget-dropTarget,
-.oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget {
- border-color: #dddddd;
- background-color: #f3f3f3;
-}
-.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info,
-.oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-info,
-.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-dropLabel,
-.oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-dropLabel {
- color: #cccccc;
- text-shadow: 0 1px 1px #ffffff;
-}
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget {
+.oo-ui-selectFileWidget-dropTarget {
background-color: #ffffff;
border: 1px solid #cccccc;
- margin-bottom: 0.5em;
vertical-align: middle;
overflow: hidden;
border-radius: 2px;
}
-.oo-ui-selectFileWidget.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button {
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-selectButton > .oo-ui-buttonElement-button {
border-radius: 2px;
}
+.oo-ui-selectFileWidget-dropTarget .oo-ui-selectFileWidget-label {
+ line-height: 1.4;
+ overflow: inherit;
+ white-space: normal;
+}
.oo-ui-selectFileWidget-empty.oo-ui-selectFileWidget-dropTarget {
background-color: #eeeeee;
border-style: dashed;
}
+.oo-ui-selectFileWidget.oo-ui-widget-disabled.oo-ui-selectFileWidget-dropTarget,
+.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled.oo-ui-selectFileWidget-dropTarget,
+.oo-ui-selectFileWidget-notsupported.oo-ui-selectFileWidget-dropTarget {
+ background-color: #f3f3f3;
+ border-color: #dddddd;
+}
+.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info,
+.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-info,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info {
+ background-color: #f3f3f3;
+ color: #cccccc;
+ border-color: #dddddd;
+ text-shadow: 0 1px 1px #ffffff;
+}
+.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
+.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info > .oo-ui-iconElement-icon,
+.oo-ui-selectFileWidget.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator,
+.oo-ui-selectFileWidget-empty.oo-ui-widget-disabled .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator,
+.oo-ui-selectFileWidget-notsupported .oo-ui-selectFileWidget-info > .oo-ui-indicatorElement-indicator {
+ opacity: 0.2;
+}
.oo-ui-widget-disabled .oo-ui-selectFileWidget-dropLabel {
display: none;
}
.oo-ui-capsuleMultiselectWidget-group {
display: inline;
}
-.oo-ui-capsuleMultiselectWidget > .oo-ui-menuSelectWidget {
- z-index: 1;
- width: 100%;
-}
.oo-ui-capsuleMultiselectWidget-handle {
- background-color: #ffffff;
- cursor: text;
min-height: 2.4em;
margin-right: 0.5em;
padding: 0.15em 0.25em;
top: 0;
margin: 0.3em;
}
-.oo-ui-capsuleMultiselectWidget:hover .oo-ui-capsuleMultiselectWidget-handle {
+.oo-ui-capsuleMultiselectWidget.oo-ui-widget-enabled .oo-ui-capsuleMultiselectWidget-handle {
+ background-color: #ffffff;
+ cursor: text;
+ -webkit-transition: border-color 100ms;
+ -moz-transition: border-color 100ms;
+ transition: border-color 100ms;
+}
+.oo-ui-capsuleMultiselectWidget.oo-ui-widget-enabled:hover .oo-ui-capsuleMultiselectWidget-handle {
border-color: #aaaaaa;
}
.oo-ui-capsuleMultiselectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiselectWidget-handle {
text-shadow: 0 1px 1px #ffffff;
border-color: #dddddd;
background-color: #f3f3f3;
- cursor: default;
}
.oo-ui-capsuleMultiselectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiselectWidget-handle > .oo-ui-iconElement-icon,
.oo-ui-capsuleMultiselectWidget.oo-ui-widget-disabled .oo-ui-capsuleMultiselectWidget-handle > .oo-ui-indicatorElement-indicator {
/*!
- * OOjs UI v0.17.7
+ * OOjs UI v0.17.8
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-08-03T16:38:22Z
+ * Date: 2016-08-16T21:13:48Z
*/
( function ( OO ) {
return this;
};
-/**
- * Progress bars visually display the status of an operation, such as a download,
- * and can be either determinate or indeterminate:
- *
- * - **determinate** process bars show the percent of an operation that is complete.
- *
- * - **indeterminate** process bars use a visual display of motion to indicate that an operation
- * is taking place. Because the extent of an indeterminate operation is unknown, the bar does
- * not use percentages.
- *
- * The value of the `progress` configuration determines whether the bar is determinate or indeterminate.
- *
- * @example
- * // Examples of determinate and indeterminate progress bars.
- * var progressBar1 = new OO.ui.ProgressBarWidget( {
- * progress: 33
- * } );
- * var progressBar2 = new OO.ui.ProgressBarWidget();
- *
- * // Create a FieldsetLayout to layout progress bars
- * var fieldset = new OO.ui.FieldsetLayout;
- * fieldset.addItems( [
- * new OO.ui.FieldLayout( progressBar1, {label: 'Determinate', align: 'top'}),
- * new OO.ui.FieldLayout( progressBar2, {label: 'Indeterminate', align: 'top'})
- * ] );
- * $( 'body' ).append( fieldset.$element );
- *
- * @class
- * @extends OO.ui.Widget
- *
- * @constructor
- * @param {Object} [config] Configuration options
- * @cfg {number|boolean} [progress=false] The type of progress bar (determinate or indeterminate).
- * To create a determinate progress bar, specify a number that reflects the initial percent complete.
- * By default, the progress bar is indeterminate.
- */
-OO.ui.ProgressBarWidget = function OoUiProgressBarWidget( config ) {
- // Configuration initialization
- config = config || {};
-
- // Parent constructor
- OO.ui.ProgressBarWidget.parent.call( this, config );
-
- // Properties
- this.$bar = $( '<div>' );
- this.progress = null;
-
- // Initialization
- this.setProgress( config.progress !== undefined ? config.progress : false );
- this.$bar.addClass( 'oo-ui-progressBarWidget-bar' );
- this.$element
- .attr( {
- role: 'progressbar',
- 'aria-valuemin': 0,
- 'aria-valuemax': 100
- } )
- .addClass( 'oo-ui-progressBarWidget' )
- .append( this.$bar );
-};
-
-/* Setup */
-
-OO.inheritClass( OO.ui.ProgressBarWidget, OO.ui.Widget );
-
-/* Static Properties */
-
-OO.ui.ProgressBarWidget.static.tagName = 'div';
-
-/* Methods */
-
-/**
- * Get the percent of the progress that has been completed. Indeterminate progresses will return `false`.
- *
- * @return {number|boolean} Progress percent
- */
-OO.ui.ProgressBarWidget.prototype.getProgress = function () {
- return this.progress;
-};
-
-/**
- * Set the percent of the process completed or `false` for an indeterminate process.
- *
- * @param {number|boolean} progress Progress percent or `false` for indeterminate
- */
-OO.ui.ProgressBarWidget.prototype.setProgress = function ( progress ) {
- this.progress = progress;
-
- if ( progress !== false ) {
- this.$bar.css( 'width', this.progress + '%' );
- this.$element.attr( 'aria-valuenow', this.progress );
- } else {
- this.$bar.css( 'width', '' );
- this.$element.removeAttr( 'aria-valuenow' );
- }
- this.$element.toggleClass( 'oo-ui-progressBarWidget-indeterminate', !progress );
-};
-
/**
* SearchWidgets combine a {@link OO.ui.TextInputWidget text input field}, where users can type a search query,
* and a menu of search results, which is displayed beneath the query
/*!
- * OOjs UI v0.17.7
+ * OOjs UI v0.17.8
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-08-03T16:38:27Z
+ * Date: 2016-08-16T21:13:53Z
*/
.oo-ui-actionWidget.oo-ui-pendingElement-pending {
background-image: /* @embed */ url(themes/apex/images/textures/pending.gif);
width: 0;
height: 0;
overflow: hidden;
+ z-index: 4;
}
.oo-ui-windowManager-modal > .oo-ui-dialog.oo-ui-window-active {
width: auto;
/*!
- * OOjs UI v0.17.7
+ * OOjs UI v0.17.8
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-08-03T16:38:27Z
+ * Date: 2016-08-16T21:13:53Z
*/
.oo-ui-window {
background: transparent;
width: 0;
height: 0;
overflow: hidden;
+ z-index: 4;
}
.oo-ui-windowManager-modal > .oo-ui-dialog.oo-ui-window-active {
width: auto;
/*!
- * OOjs UI v0.17.7
+ * OOjs UI v0.17.8
* https://www.mediawiki.org/wiki/OOjs_UI
*
* Copyright 2011–2016 OOjs UI Team and other contributors.
* Released under the MIT license
* http://oojs.mit-license.org
*
- * Date: 2016-08-03T16:38:22Z
+ * Date: 2016-08-16T21:13:48Z
*/
( function ( OO ) {
margin: .5em 1.4em 1.3em 0;
}
-/* Hide elements that are marked as "empty" according to legacy Tidy rules
+/* Hide elements that are marked as "empty" according to legacy Tidy rules,
+ * except if a client script removes the mw-hide-empty-elt class from the body
*/
-.mw-empty-elt, .mw-empty-li {
+body.mw-hide-empty-elt .mw-empty-elt {
display: none;
}
+++ /dev/null
-/*!
- * Skip function for OOjs UI PHP style modules.
- *
- * The `<meta name="X-OOUI-PHP" />` is added to pages by OutputPage::enableOOUI().
- *
- * Looking for elements in the DOM might be expensive, but it's probably better than double-loading
- * 200 KB of CSS with embedded images because of bug T87871.
- */
-return !!jQuery( 'meta[name="X-OOUI-PHP"]' ).length;
mediawiki_url: http://127.0.0.1/wiki/
beta:
- mediawiki_url: http://en.wikipedia.beta.wmflabs.org/wiki/
+ mediawiki_url: https://en.wikipedia.beta.wmflabs.org/wiki/
mediawiki_user: Selenium_user
# mediawiki_password: SET THIS IN THE ENVIRONMENT!
test2:
- mediawiki_url: http://test2.wikipedia.org/wiki/
+ mediawiki_url: https://test2.wikipedia.org/wiki/
mediawiki_user: Selenium_user
# mediawiki_password: SET THIS IN THE ENVIRONMENT!
echo "Warning: tidy is not installed, skipping some tests\n";
}
- if ( !extension_loaded( 'gd' ) ) {
- echo "Warning: GD extension is not present, thumbnailing tests will probably fail\n";
- }
-
$this->hooks = [];
$this->functionHooks = [];
$this->transparentHooks = [];
$actual = $this->manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN );
$expected = [
$rememberReq,
- $makeReq( "primary-shared", AuthenticationRequest::REQUIRED ),
+ $makeReq( "primary-shared", AuthenticationRequest::PRIMARY_REQUIRED ),
$makeReq( "required", AuthenticationRequest::PRIMARY_REQUIRED ),
$makeReq( "required2", AuthenticationRequest::PRIMARY_REQUIRED ),
$makeReq( "optional", AuthenticationRequest::OPTIONAL ),
$actual = $this->manager->getAuthenticationRequests( AuthManager::ACTION_LOGIN );
$expected = [
$rememberReq,
- $makeReq( "primary-shared", AuthenticationRequest::REQUIRED ),
- $makeReq( "required", AuthenticationRequest::REQUIRED ),
+ $makeReq( "primary-shared", AuthenticationRequest::PRIMARY_REQUIRED ),
+ $makeReq( "required", AuthenticationRequest::PRIMARY_REQUIRED ),
$makeReq( "optional", AuthenticationRequest::OPTIONAL ),
- $makeReq( "foo", AuthenticationRequest::REQUIRED ),
+ $makeReq( "foo", AuthenticationRequest::PRIMARY_REQUIRED ),
$makeReq( "bar", AuthenticationRequest::REQUIRED ),
$makeReq( "baz", AuthenticationRequest::REQUIRED ),
];
'type' => 'string',
'label' => $msg,
'help' => $msg,
+ 'sensitive' => true,
],
'string3' => [
'type' => 'string',
$expect = $req1->getFieldInfo();
foreach ( $expect as $name => &$options ) {
$options['optional'] = !empty( $options['optional'] );
+ $options['sensitive'] = !empty( $options['sensitive'] );
}
unset( $options );
$this->assertEquals( $expect, $fields );
$fields = AuthenticationRequest::mergeFieldInfo( [ $req1, $req2 ] );
$expect += $req2->getFieldInfo();
+ $expect['string1']['sensitive'] = true;
$expect['string2']['optional'] = false;
$expect['string3']['optional'] = false;
+ $expect['string3']['sensitive'] = false;
$expect['select']['options']['bar'] = $msg;
$this->assertEquals( $expect, $fields );
$fields = AuthenticationRequest::mergeFieldInfo( [ $req1, $req2 ] );
$expect += $req2->getFieldInfo();
$expect['string1']['optional'] = false;
+ $expect['string1']['sensitive'] = true;
$expect['string3']['optional'] = false;
$expect['select']['optional'] = false;
$expect['select']['options']['bar'] = $msg;
$fields = AuthenticationRequest::mergeFieldInfo( [ $req1, $req2 ] );
$expect = $req1->getFieldInfo() + $req2->getFieldInfo();
+ foreach ( $expect as $name => &$options ) {
+ $options['sensitive'] = !empty( $options['sensitive'] );
+ }
$expect['string1']['optional'] = false;
+ $expect['string1']['sensitive'] = true;
$expect['string2']['optional'] = true;
$expect['string3']['optional'] = true;
$expect['select']['optional'] = false;
if ( isset( $data['image'] ) ) {
$this->assertType( 'string', $data['image'], "Field $field, image" );
}
+ if ( isset( $data['sensitive'] ) ) {
+ $this->assertType( 'bool', $data['sensitive'], "Field $field, sensitive" );
+ }
+ if ( $data['type'] === 'password' ) {
+ $this->assertTrue( !empty( $data['sensitive'] ),
+ "Field $field, password field must be sensitive" );
+ }
switch ( $data['type'] ) {
case 'string':
$content = new WikitextContent( 'test text' );
$ok = ContentHandler::runLegacyHooks(
'testRunLegacyHooks',
- [ 'foo', &$content, 'bar' ],
- false
+ [ 'foo', &$content, 'bar' ]
);
$this->assertTrue( $ok, "runLegacyHooks should have returned true" );
$called = true;
$db->setFlag( DBO_TRX );
} );
- $db->rollback( __METHOD__ );
+ $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS );
$this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
$this->assertTrue( $called, 'Callback reached' );
}
+
+ public function testGetScopedLock() {
+ $db = $this->db;
+
+ $db->setFlag( DBO_TRX );
+ try {
+ $this->badLockingMethodImplicit( $db );
+ } catch ( RunTimeException $e ) {
+ $this->assertTrue( $db->trxLevel() > 0, "Transaction not committed." );
+ }
+ $db->clearFlag( DBO_TRX );
+ $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS );
+ $this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ) );
+
+ try {
+ $this->badLockingMethodExplicit( $db );
+ } catch ( RunTimeException $e ) {
+ $this->assertTrue( $db->trxLevel() > 0, "Transaction not committed." );
+ }
+ $db->rollback( __METHOD__, IDatabase::FLUSHING_ALL_PEERS );
+ $this->assertTrue( $db->lockIsFree( 'meow', __METHOD__ ) );
+ }
+
+ private function badLockingMethodImplicit( IDatabase $db ) {
+ $lock = $db->getScopedLockAndFlush( 'meow', __METHOD__, 1 );
+ $db->query( "SELECT 1" ); // trigger DBO_TRX
+ throw new RunTimeException( "Uh oh!" );
+ }
+
+ private function badLockingMethodExplicit( IDatabase $db ) {
+ $lock = $db->getScopedLockAndFlush( 'meow', __METHOD__, 1 );
+ $db->begin( __METHOD__ );
+ throw new RunTimeException( "Uh oh!" );
+ }
}
/**
* @covers ObjectFactory::getObjectFromSpec
+ * @covers ObjectFactory::expandClosures
*/
public function testClosureExpansionEnabled() {
$obj = ObjectFactory::getObjectFromSpec( [
$this->assertSame( 'unwrapped', $obj->setterArgs[0] );
}
+ /**
+ * @covers ObjectFactory::getObjectFromSpec
+ */
+ public function testGetObjectFromFactory() {
+ $args = [ 'a', 'b' ];
+ $obj = ObjectFactory::getObjectFromSpec( [
+ 'factory' => function ( $a, $b ) {
+ return new ObjectFactoryTestFixture( $a, $b );
+ },
+ 'args' => $args,
+ ] );
+ $this->assertSame( $args, $obj->args );
+ }
+
+ /**
+ * @covers ObjectFactory::getObjectFromSpec
+ * @expectedException InvalidArgumentException
+ */
+ public function testGetObjectFromInvalid() {
+ $args = [ 'a', 'b' ];
+ $obj = ObjectFactory::getObjectFromSpec( [
+ // Missing 'class' or 'factory'
+ 'args' => $args,
+ ] );
+ }
+
+ /**
+ * @covers ObjectFactory::getObjectFromSpec
+ * @dataProvider provideConstructClassInstance
+ */
+ public function testGetObjectFromClass( $args ) {
+ $obj = ObjectFactory::getObjectFromSpec( [
+ 'class' => 'ObjectFactoryTestFixture',
+ 'args' => $args,
+ ] );
+ $this->assertSame( $args, $obj->args );
+ }
+
/**
* @covers ObjectFactory::constructClassInstance
* @dataProvider provideConstructClassInstance
$this->assertSame( $args, $obj->args );
}
- public function provideConstructClassInstance() {
+ public static function provideConstructClassInstance() {
// These args go to 11. I thought about making 10 one louder, but 11!
return [
'0 args' => [ [] ],
}
/**
+ * @covers ObjectFactory::constructClassInstance
* @expectedException InvalidArgumentException
*/
public function testNamedArgs() {
}
}
+ /**
+ * Verify that all specified messages actually exist.
+ */
+ public function testMissingMessages() {
+ $data = self::getAllModules();
+ $validDeps = array_keys( $data['modules'] );
+ $lang = Language::factory( 'en' );
+
+ /** @var ResourceLoaderModule $module */
+ foreach ( $data['modules'] as $moduleName => $module ) {
+ foreach ( $module->getMessages() as $msgKey ) {
+ $this->assertTrue(
+ wfMessage( $msgKey )->useDatabase( false )->inLanguage( $lang )->exists(),
+ "Message '$msgKey' required by '$moduleName' must exist"
+ );
+ }
+ }
+ }
+
/**
* Verify that all dependencies of all modules are always satisfiable with the 'targets' defined
* for the involved modules.
'tests/qunit/suites/resources/mediawiki/mediawiki.template.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.template.mustache.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.test.js',
+ 'tests/qunit/suites/resources/mediawiki/mediawiki.loader.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.html.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.Title.test.js',
'tests/qunit/suites/resources/mediawiki/mediawiki.toc.test.js',
--- /dev/null
+( function ( mw, $ ) {
+ QUnit.module( 'mediawiki (mw.loader)' );
+
+ mw.loader.addSource(
+ 'testloader',
+ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/load.mock.php' )
+ );
+
+ /**
+ * The sync style load test (for @import). This is, in a way, also an open bug for
+ * ResourceLoader ("execute js after styles are loaded"), but browsers don't offer a
+ * way to get a callback from when a stylesheet is loaded (that is, including any
+ * `@import` rules inside). To work around this, we'll have a little time loop to check
+ * if the styles apply.
+ *
+ * Note: This test originally used new Image() and onerror to get a callback
+ * when the url is loaded, but that is fragile since it doesn't monitor the
+ * same request as the css @import, and Safari 4 has issues with
+ * onerror/onload not being fired at all in weird cases like this.
+ */
+ function assertStyleAsync( assert, $element, prop, val, fn ) {
+ var styleTestStart,
+ el = $element.get( 0 ),
+ styleTestTimeout = ( QUnit.config.testTimeout || 5000 ) - 200;
+
+ function isCssImportApplied() {
+ // Trigger reflow, repaint, redraw, whatever (cross-browser)
+ var x = $element.css( 'height' );
+ x = el.innerHTML;
+ el.className = el.className;
+ x = document.documentElement.clientHeight;
+
+ return $element.css( prop ) === val;
+ }
+
+ function styleTestLoop() {
+ var styleTestSince = new Date().getTime() - styleTestStart;
+ // If it is passing or if we timed out, run the real test and stop the loop
+ if ( isCssImportApplied() || styleTestSince > styleTestTimeout ) {
+ assert.equal( $element.css( prop ), val,
+ 'style "' + prop + ': ' + val + '" from url is applied (after ' + styleTestSince + 'ms)'
+ );
+
+ if ( fn ) {
+ fn();
+ }
+
+ return;
+ }
+ // Otherwise, keep polling
+ setTimeout( styleTestLoop );
+ }
+
+ // Start the loop
+ styleTestStart = new Date().getTime();
+ styleTestLoop();
+ }
+
+ function urlStyleTest( selector, prop, val ) {
+ return QUnit.fixurl(
+ mw.config.get( 'wgScriptPath' ) +
+ '/tests/qunit/data/styleTest.css.php?' +
+ $.param( {
+ selector: selector,
+ prop: prop,
+ val: val
+ } )
+ );
+ }
+
+ QUnit.test( 'Basic', 2, function ( assert ) {
+ var isAwesomeDone;
+
+ mw.loader.testCallback = function () {
+ assert.strictEqual( isAwesomeDone, undefined, 'Implementing module is.awesome: isAwesomeDone should still be undefined' );
+ isAwesomeDone = true;
+ };
+
+ mw.loader.implement( 'test.callback', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' ) ] );
+
+ return mw.loader.using( 'test.callback', function () {
+ assert.strictEqual( isAwesomeDone, true, 'test.callback module should\'ve caused isAwesomeDone to be true' );
+ delete mw.loader.testCallback;
+
+ }, function () {
+ assert.ok( false, 'Error callback fired while loader.using "test.callback" module' );
+ } );
+ } );
+
+ QUnit.test( 'Object method as module name', 2, function ( assert ) {
+ var isAwesomeDone;
+
+ mw.loader.testCallback = function () {
+ assert.strictEqual( isAwesomeDone, undefined, 'Implementing module hasOwnProperty: isAwesomeDone should still be undefined' );
+ isAwesomeDone = true;
+ };
+
+ mw.loader.implement( 'hasOwnProperty', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' ) ], {}, {} );
+
+ return mw.loader.using( 'hasOwnProperty', function () {
+ assert.strictEqual( isAwesomeDone, true, 'hasOwnProperty module should\'ve caused isAwesomeDone to be true' );
+ delete mw.loader.testCallback;
+
+ }, function () {
+ assert.ok( false, 'Error callback fired while loader.using "hasOwnProperty" module' );
+ } );
+ } );
+
+ QUnit.test( '.using( .. ) Promise', 2, function ( assert ) {
+ var isAwesomeDone;
+
+ mw.loader.testCallback = function () {
+ assert.strictEqual( isAwesomeDone, undefined, 'Implementing module is.awesome: isAwesomeDone should still be undefined' );
+ isAwesomeDone = true;
+ };
+
+ mw.loader.implement( 'test.promise', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' ) ] );
+
+ return mw.loader.using( 'test.promise' )
+ .done( function () {
+ assert.strictEqual( isAwesomeDone, true, 'test.promise module should\'ve caused isAwesomeDone to be true' );
+ delete mw.loader.testCallback;
+
+ } )
+ .fail( function () {
+ assert.ok( false, 'Error callback fired while loader.using "test.promise" module' );
+ } );
+ } );
+
+ QUnit.test( '.implement( styles={ "css": [text, ..] } )', 2, function ( assert ) {
+ var $element = $( '<div class="mw-test-implement-a"></div>' ).appendTo( '#qunit-fixture' );
+
+ assert.notEqual(
+ $element.css( 'float' ),
+ 'right',
+ 'style is clear'
+ );
+
+ mw.loader.implement(
+ 'test.implement.a',
+ function () {
+ assert.equal(
+ $element.css( 'float' ),
+ 'right',
+ 'style is applied'
+ );
+ },
+ {
+ all: '.mw-test-implement-a { float: right; }'
+ }
+ );
+
+ return mw.loader.using( 'test.implement.a' );
+ } );
+
+ QUnit.test( '.implement( styles={ "url": { <media>: [url, ..] } } )', 7, function ( assert ) {
+ var $element1 = $( '<div class="mw-test-implement-b1"></div>' ).appendTo( '#qunit-fixture' ),
+ $element2 = $( '<div class="mw-test-implement-b2"></div>' ).appendTo( '#qunit-fixture' ),
+ $element3 = $( '<div class="mw-test-implement-b3"></div>' ).appendTo( '#qunit-fixture' ),
+ done = assert.async();
+
+ assert.notEqual(
+ $element1.css( 'text-align' ),
+ 'center',
+ 'style is clear'
+ );
+ assert.notEqual(
+ $element2.css( 'float' ),
+ 'left',
+ 'style is clear'
+ );
+ assert.notEqual(
+ $element3.css( 'text-align' ),
+ 'right',
+ 'style is clear'
+ );
+
+ mw.loader.implement(
+ 'test.implement.b',
+ function () {
+ // Note: done() must only be called when the entire test is
+ // complete. So, make sure that we don't start until *both*
+ // assertStyleAsync calls have completed.
+ var pending = 2;
+ assertStyleAsync( assert, $element2, 'float', 'left', function () {
+ assert.notEqual( $element1.css( 'text-align' ), 'center', 'print style is not applied' );
+
+ pending--;
+ if ( pending === 0 ) {
+ done();
+ }
+ } );
+ assertStyleAsync( assert, $element3, 'float', 'right', function () {
+ assert.notEqual( $element1.css( 'text-align' ), 'center', 'print style is not applied' );
+
+ pending--;
+ if ( pending === 0 ) {
+ done();
+ }
+ } );
+ },
+ {
+ url: {
+ print: [ urlStyleTest( '.mw-test-implement-b1', 'text-align', 'center' ) ],
+ screen: [
+ // bug 40834: Make sure it actually works with more than 1 stylesheet reference
+ urlStyleTest( '.mw-test-implement-b2', 'float', 'left' ),
+ urlStyleTest( '.mw-test-implement-b3', 'float', 'right' )
+ ]
+ }
+ }
+ );
+
+ mw.loader.load( 'test.implement.b' );
+ } );
+
+ // Backwards compatibility
+ QUnit.test( '.implement( styles={ <media>: text } ) (back-compat)', 2, function ( assert ) {
+ var $element = $( '<div class="mw-test-implement-c"></div>' ).appendTo( '#qunit-fixture' );
+
+ assert.notEqual(
+ $element.css( 'float' ),
+ 'right',
+ 'style is clear'
+ );
+
+ mw.loader.implement(
+ 'test.implement.c',
+ function () {
+ assert.equal(
+ $element.css( 'float' ),
+ 'right',
+ 'style is applied'
+ );
+ },
+ {
+ all: '.mw-test-implement-c { float: right; }'
+ }
+ );
+
+ return mw.loader.using( 'test.implement.c' );
+ } );
+
+ // Backwards compatibility
+ QUnit.test( '.implement( styles={ <media>: [url, ..] } ) (back-compat)', 4, function ( assert ) {
+ var $element = $( '<div class="mw-test-implement-d"></div>' ).appendTo( '#qunit-fixture' ),
+ $element2 = $( '<div class="mw-test-implement-d2"></div>' ).appendTo( '#qunit-fixture' ),
+ done = assert.async();
+
+ assert.notEqual(
+ $element.css( 'float' ),
+ 'right',
+ 'style is clear'
+ );
+ assert.notEqual(
+ $element2.css( 'text-align' ),
+ 'center',
+ 'style is clear'
+ );
+
+ mw.loader.implement(
+ 'test.implement.d',
+ function () {
+ assertStyleAsync( assert, $element, 'float', 'right', function () {
+ assert.notEqual( $element2.css( 'text-align' ), 'center', 'print style is not applied (bug 40500)' );
+ done();
+ } );
+ },
+ {
+ all: [ urlStyleTest( '.mw-test-implement-d', 'float', 'right' ) ],
+ print: [ urlStyleTest( '.mw-test-implement-d2', 'text-align', 'center' ) ]
+ }
+ );
+
+ mw.loader.load( 'test.implement.d' );
+ } );
+
+ // @import (bug 31676)
+ QUnit.test( '.implement( styles has @import )', 7, function ( assert ) {
+ var isJsExecuted, $element,
+ done = assert.async();
+
+ mw.loader.implement(
+ 'test.implement.import',
+ function () {
+ assert.strictEqual( isJsExecuted, undefined, 'script not executed multiple times' );
+ isJsExecuted = true;
+
+ assert.equal( mw.loader.getState( 'test.implement.import' ), 'executing', 'module state during implement() script execution' );
+
+ $element = $( '<div class="mw-test-implement-import">Foo bar</div>' ).appendTo( '#qunit-fixture' );
+
+ assert.equal( mw.msg( 'test-foobar' ), 'Hello Foobar, $1!', 'messages load before script execution' );
+
+ assertStyleAsync( assert, $element, 'float', 'right', function () {
+ assert.equal( $element.css( 'text-align' ), 'center',
+ 'CSS styles after the @import rule are working'
+ );
+
+ done();
+ } );
+ },
+ {
+ css: [
+ '@import url(\''
+ + urlStyleTest( '.mw-test-implement-import', 'float', 'right' )
+ + '\');\n'
+ + '.mw-test-implement-import { text-align: center; }'
+ ]
+ },
+ {
+ 'test-foobar': 'Hello Foobar, $1!'
+ }
+ );
+
+ mw.loader.using( 'test.implement.import' ).always( function () {
+ assert.strictEqual( isJsExecuted, true, 'script executed' );
+ assert.equal( mw.loader.getState( 'test.implement.import' ), 'ready', 'module state after script execution' );
+ } );
+ } );
+
+ QUnit.test( '.implement( dependency with styles )', 4, function ( assert ) {
+ var $element = $( '<div class="mw-test-implement-e"></div>' ).appendTo( '#qunit-fixture' ),
+ $element2 = $( '<div class="mw-test-implement-e2"></div>' ).appendTo( '#qunit-fixture' );
+
+ assert.notEqual(
+ $element.css( 'float' ),
+ 'right',
+ 'style is clear'
+ );
+ assert.notEqual(
+ $element2.css( 'float' ),
+ 'left',
+ 'style is clear'
+ );
+
+ mw.loader.register( [
+ [ 'test.implement.e', '0', [ 'test.implement.e2' ] ],
+ [ 'test.implement.e2', '0' ]
+ ] );
+
+ mw.loader.implement(
+ 'test.implement.e',
+ function () {
+ assert.equal(
+ $element.css( 'float' ),
+ 'right',
+ 'Depending module\'s style is applied'
+ );
+ },
+ {
+ all: '.mw-test-implement-e { float: right; }'
+ }
+ );
+
+ mw.loader.implement(
+ 'test.implement.e2',
+ function () {
+ assert.equal(
+ $element2.css( 'float' ),
+ 'left',
+ 'Dependency\'s style is applied'
+ );
+ },
+ {
+ all: '.mw-test-implement-e2 { float: left; }'
+ }
+ );
+
+ return mw.loader.using( 'test.implement.e' );
+ } );
+
+ QUnit.test( '.implement( only scripts )', 1, function ( assert ) {
+ mw.loader.implement( 'test.onlyscripts', function () {} );
+ assert.strictEqual( mw.loader.getState( 'test.onlyscripts' ), 'ready' );
+ } );
+
+ QUnit.test( '.implement( only messages )', 2, function ( assert ) {
+ assert.assertFalse( mw.messages.exists( 'bug_29107' ), 'Verify that the test message doesn\'t exist yet' );
+
+ // jscs: disable requireCamelCaseOrUpperCaseIdentifiers
+ mw.loader.implement( 'test.implement.msgs', [], {}, { bug_29107: 'loaded' } );
+ // jscs: enable requireCamelCaseOrUpperCaseIdentifiers
+
+ return mw.loader.using( 'test.implement.msgs', function () {
+ assert.ok( mw.messages.exists( 'bug_29107' ), 'Bug 29107: messages-only module should implement ok' );
+ }, function () {
+ assert.ok( false, 'Error callback fired while implementing "test.implement.msgs" module' );
+ } );
+ } );
+
+ QUnit.test( '.implement( empty )', 1, function ( assert ) {
+ mw.loader.implement( 'test.empty' );
+ assert.strictEqual( mw.loader.getState( 'test.empty' ), 'ready' );
+ } );
+
+ QUnit.test( 'Broken indirect dependency', 4, function ( assert ) {
+ // don't emit an error event
+ this.sandbox.stub( mw, 'track' );
+
+ mw.loader.register( [
+ [ 'test.module1', '0' ],
+ [ 'test.module2', '0', [ 'test.module1' ] ],
+ [ 'test.module3', '0', [ 'test.module2' ] ]
+ ] );
+ mw.loader.implement( 'test.module1', function () {
+ throw new Error( 'expected' );
+ }, {}, {} );
+ assert.strictEqual( mw.loader.getState( 'test.module1' ), 'error', 'Expected "error" state for test.module1' );
+ assert.strictEqual( mw.loader.getState( 'test.module2' ), 'error', 'Expected "error" state for test.module2' );
+ assert.strictEqual( mw.loader.getState( 'test.module3' ), 'error', 'Expected "error" state for test.module3' );
+
+ assert.strictEqual( mw.track.callCount, 1 );
+ } );
+
+ QUnit.test( 'Circular dependency', 1, function ( assert ) {
+ mw.loader.register( [
+ [ 'test.circle1', '0', [ 'test.circle2' ] ],
+ [ 'test.circle2', '0', [ 'test.circle3' ] ],
+ [ 'test.circle3', '0', [ 'test.circle1' ] ]
+ ] );
+ assert.throws( function () {
+ mw.loader.using( 'test.circle3' );
+ }, /Circular/, 'Detect circular dependency' );
+ } );
+
+ QUnit.test( 'Out-of-order implementation', 9, function ( assert ) {
+ mw.loader.register( [
+ [ 'test.module4', '0' ],
+ [ 'test.module5', '0', [ 'test.module4' ] ],
+ [ 'test.module6', '0', [ 'test.module5' ] ]
+ ] );
+ mw.loader.implement( 'test.module4', function () {} );
+ assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
+ assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' );
+ assert.strictEqual( mw.loader.getState( 'test.module6' ), 'registered', 'Expected "registered" state for test.module6' );
+ mw.loader.implement( 'test.module6', function () {} );
+ assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
+ assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' );
+ assert.strictEqual( mw.loader.getState( 'test.module6' ), 'loaded', 'Expected "loaded" state for test.module6' );
+ mw.loader.implement( 'test.module5', function () {} );
+ assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
+ assert.strictEqual( mw.loader.getState( 'test.module5' ), 'ready', 'Expected "ready" state for test.module5' );
+ assert.strictEqual( mw.loader.getState( 'test.module6' ), 'ready', 'Expected "ready" state for test.module6' );
+ } );
+
+ QUnit.test( 'Missing dependency', 13, function ( assert ) {
+ mw.loader.register( [
+ [ 'test.module7', '0' ],
+ [ 'test.module8', '0', [ 'test.module7' ] ],
+ [ 'test.module9', '0', [ 'test.module8' ] ]
+ ] );
+ mw.loader.implement( 'test.module8', function () {} );
+ assert.strictEqual( mw.loader.getState( 'test.module7' ), 'registered', 'Expected "registered" state for test.module7' );
+ assert.strictEqual( mw.loader.getState( 'test.module8' ), 'loaded', 'Expected "loaded" state for test.module8' );
+ assert.strictEqual( mw.loader.getState( 'test.module9' ), 'registered', 'Expected "registered" state for test.module9' );
+ mw.loader.state( 'test.module7', 'missing' );
+ assert.strictEqual( mw.loader.getState( 'test.module7' ), 'missing', 'Expected "missing" state for test.module7' );
+ assert.strictEqual( mw.loader.getState( 'test.module8' ), 'error', 'Expected "error" state for test.module8' );
+ assert.strictEqual( mw.loader.getState( 'test.module9' ), 'error', 'Expected "error" state for test.module9' );
+ mw.loader.implement( 'test.module9', function () {} );
+ assert.strictEqual( mw.loader.getState( 'test.module7' ), 'missing', 'Expected "missing" state for test.module7' );
+ assert.strictEqual( mw.loader.getState( 'test.module8' ), 'error', 'Expected "error" state for test.module8' );
+ assert.strictEqual( mw.loader.getState( 'test.module9' ), 'error', 'Expected "error" state for test.module9' );
+ mw.loader.using(
+ [ 'test.module7' ],
+ function () {
+ assert.ok( false, 'Success fired despite missing dependency' );
+ assert.ok( true, 'QUnit expected() count dummy' );
+ },
+ function ( e, dependencies ) {
+ assert.strictEqual( $.isArray( dependencies ), true, 'Expected array of dependencies' );
+ assert.deepEqual( dependencies, [ 'test.module7' ], 'Error callback called with module test.module7' );
+ }
+ );
+ mw.loader.using(
+ [ 'test.module9' ],
+ function () {
+ assert.ok( false, 'Success fired despite missing dependency' );
+ assert.ok( true, 'QUnit expected() count dummy' );
+ },
+ function ( e, dependencies ) {
+ assert.strictEqual( $.isArray( dependencies ), true, 'Expected array of dependencies' );
+ dependencies.sort();
+ assert.deepEqual(
+ dependencies,
+ [ 'test.module7', 'test.module8', 'test.module9' ],
+ 'Error callback called with all three modules as dependencies'
+ );
+ }
+ );
+ } );
+
+ QUnit.test( 'Dependency handling', 5, function ( assert ) {
+ var done = assert.async();
+ mw.loader.register( [
+ // [module, version, dependencies, group, source]
+ [ 'testMissing', '1', [], null, 'testloader' ],
+ [ 'testUsesMissing', '1', [ 'testMissing' ], null, 'testloader' ],
+ [ 'testUsesNestedMissing', '1', [ 'testUsesMissing' ], null, 'testloader' ]
+ ] );
+
+ function verifyModuleStates() {
+ assert.equal( mw.loader.getState( 'testMissing' ), 'missing', 'Module not known to server must have state "missing"' );
+ assert.equal( mw.loader.getState( 'testUsesMissing' ), 'error', 'Module with missing dependency must have state "error"' );
+ assert.equal( mw.loader.getState( 'testUsesNestedMissing' ), 'error', 'Module with indirect missing dependency must have state "error"' );
+ }
+
+ mw.loader.using( [ 'testUsesNestedMissing' ],
+ function () {
+ assert.ok( false, 'Error handler should be invoked.' );
+ assert.ok( true ); // Dummy to reach QUnit expect()
+
+ verifyModuleStates();
+
+ done();
+ },
+ function ( e, badmodules ) {
+ assert.ok( true, 'Error handler should be invoked.' );
+ // As soon as server spits out state('testMissing', 'missing');
+ // it will bubble up and trigger the error callback.
+ // Therefor the badmodules array is not testUsesMissing or testUsesNestedMissing.
+ assert.deepEqual( badmodules, [ 'testMissing' ], 'Bad modules as expected.' );
+
+ verifyModuleStates();
+
+ done();
+ }
+ );
+ } );
+
+ QUnit.test( 'Skip-function handling', 5, function ( assert ) {
+ mw.loader.register( [
+ // [module, version, dependencies, group, source, skip]
+ [ 'testSkipped', '1', [], null, 'testloader', 'return true;' ],
+ [ 'testNotSkipped', '1', [], null, 'testloader', 'return false;' ],
+ [ 'testUsesSkippable', '1', [ 'testSkipped', 'testNotSkipped' ], null, 'testloader' ]
+ ] );
+
+ function verifyModuleStates() {
+ assert.equal( mw.loader.getState( 'testSkipped' ), 'ready', 'Module is ready when skipped' );
+ assert.equal( mw.loader.getState( 'testNotSkipped' ), 'ready', 'Module is ready when not skipped but loaded' );
+ assert.equal( mw.loader.getState( 'testUsesSkippable' ), 'ready', 'Module is ready when skippable dependencies are ready' );
+ }
+
+ return mw.loader.using( [ 'testUsesSkippable' ],
+ function () {
+ assert.ok( true, 'Success handler should be invoked.' );
+ assert.ok( true ); // Dummy to match error handler and reach QUnit expect()
+
+ verifyModuleStates();
+ },
+ function ( e, badmodules ) {
+ assert.ok( false, 'Error handler should not be invoked.' );
+ assert.deepEqual( badmodules, [], 'Bad modules as expected.' );
+
+ verifyModuleStates();
+ }
+ );
+ } );
+
+ QUnit.asyncTest( '.load( "//protocol-relative" ) - T32825', 2, function ( assert ) {
+ // This bug was actually already fixed in 1.18 and later when discovered in 1.17.
+ // Test is for regressions!
+
+ // Forge a URL to the test callback script
+ var target = QUnit.fixurl(
+ mw.config.get( 'wgServer' ) + mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/qunitOkCall.js'
+ );
+
+ // Confirm that mw.loader.load() works with protocol-relative URLs
+ target = target.replace( /https?:/, '' );
+
+ assert.equal( target.slice( 0, 2 ), '//',
+ 'URL must be relative to test relative URLs!'
+ );
+
+ // Async!
+ // The target calls QUnit.start
+ mw.loader.load( target );
+ } );
+
+ QUnit.asyncTest( '.load( "/absolute-path" )', 2, function ( assert ) {
+ // Forge a URL to the test callback script
+ var target = QUnit.fixurl(
+ mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/qunitOkCall.js'
+ );
+
+ // Confirm that mw.loader.load() works with absolute-paths (relative to current hostname)
+ assert.equal( target.slice( 0, 1 ), '/', 'URL is relative to document root' );
+
+ // Async!
+ // The target calls QUnit.start
+ mw.loader.load( target );
+ } );
+
+ QUnit.test( 'Executing race - T112232', 2, function ( assert ) {
+ var done = false;
+
+ // The red herring schedules its CSS buffer first. In T112232, a bug in the
+ // state machine would cause the job for testRaceLoadMe to run with an earlier job.
+ mw.loader.implement(
+ 'testRaceRedHerring',
+ function () {},
+ { css: [ '.mw-testRaceRedHerring {}' ] }
+ );
+ mw.loader.implement(
+ 'testRaceLoadMe',
+ function () {
+ done = true;
+ },
+ { css: [ '.mw-testRaceLoadMe { float: left; }' ] }
+ );
+
+ mw.loader.load( [ 'testRaceRedHerring', 'testRaceLoadMe' ] );
+ return mw.loader.using( 'testRaceLoadMe', function () {
+ assert.strictEqual( done, true, 'script ran' );
+ assert.strictEqual( mw.loader.getState( 'testRaceLoadMe' ), 'ready', 'state' );
+ } );
+ } );
+
+ QUnit.test( 'require()', 6, function ( assert ) {
+ mw.loader.register( [
+ [ 'test.require1', '0' ],
+ [ 'test.require2', '0' ],
+ [ 'test.require3', '0' ],
+ [ 'test.require4', '0', [ 'test.require3' ] ]
+ ] );
+ mw.loader.implement( 'test.require1', function () {} );
+ mw.loader.implement( 'test.require2', function ( $, jQuery, require, module ) {
+ module.exports = 1;
+ } );
+ mw.loader.implement( 'test.require3', function ( $, jQuery, require, module ) {
+ module.exports = function () {
+ return 'hello world';
+ };
+ } );
+ mw.loader.implement( 'test.require4', function ( $, jQuery, require, module ) {
+ var other = require( 'test.require3' );
+ module.exports = {
+ pizza: function () {
+ return other();
+ }
+ };
+ } );
+ return mw.loader.using( [ 'test.require1', 'test.require2', 'test.require3', 'test.require4' ] )
+ .then( function ( require ) {
+ var module1, module2, module3, module4;
+
+ module1 = require( 'test.require1' );
+ module2 = require( 'test.require2' );
+ module3 = require( 'test.require3' );
+ module4 = require( 'test.require4' );
+
+ assert.strictEqual( typeof module1, 'object', 'export of module with no export' );
+ assert.strictEqual( module2, 1, 'export a number' );
+ assert.strictEqual( module3(), 'hello world', 'export a function' );
+ assert.strictEqual( typeof module4.pizza, 'function', 'export an object' );
+ assert.strictEqual( module4.pizza(), 'hello world', 'module can require other modules' );
+
+ assert.throws( function () {
+ require( '_badmodule' );
+ }, /is not loaded/, 'Requesting non-existent modules throws error.' );
+ } );
+ } );
+
+ QUnit.test( 'require() in debug mode', 1, function ( assert ) {
+ var path = mw.config.get( 'wgScriptPath' );
+ mw.loader.register( [
+ [ 'test.require.define', '0' ],
+ [ 'test.require.callback', '0', [ 'test.require.define' ] ]
+ ] );
+ mw.loader.implement( 'test.require.callback', [ QUnit.fixurl( path + '/tests/qunit/data/requireCallMwLoaderTestCallback.js' ) ] );
+ mw.loader.implement( 'test.require.define', [ QUnit.fixurl( path + '/tests/qunit/data/defineCallMwLoaderTestCallback.js' ) ] );
+
+ return mw.loader.using( 'test.require.callback' ).then( function ( require ) {
+ var exported = require( 'test.require.callback' );
+ assert.strictEqual( exported, 'Require worked.Define worked.',
+ 'module.exports worked in debug mode' );
+ }, function () {
+ assert.ok( false, 'Error callback fired while loader.using "test.require.callback" module' );
+ } );
+ } );
+
+}( mediaWiki, jQuery ) );
/*jshint -W024 */
-( function ( mw, $ ) {
+( function ( mw ) {
var specialCharactersPageName,
// Can't mock SITENAME since jqueryMsg caches it at load
siteName = mw.config.get( 'wgSiteName' );
}
} ) );
- mw.loader.addSource(
- 'testloader',
- QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/load.mock.php' )
- );
-
QUnit.test( 'Initial check', 8, function ( assert ) {
assert.ok( window.jQuery, 'jQuery defined' );
assert.ok( window.$, '$ defined' );
assert.equal( mw.msg( 'int-msg' ), 'Some Other Message', 'int is resolved' );
} );
- /**
- * The sync style load test (for @import). This is, in a way, also an open bug for
- * ResourceLoader ("execute js after styles are loaded"), but browsers don't offer a
- * way to get a callback from when a stylesheet is loaded (that is, including any
- * `@import` rules inside). To work around this, we'll have a little time loop to check
- * if the styles apply.
- *
- * Note: This test originally used new Image() and onerror to get a callback
- * when the url is loaded, but that is fragile since it doesn't monitor the
- * same request as the css @import, and Safari 4 has issues with
- * onerror/onload not being fired at all in weird cases like this.
- */
- function assertStyleAsync( assert, $element, prop, val, fn ) {
- var styleTestStart,
- el = $element.get( 0 ),
- styleTestTimeout = ( QUnit.config.testTimeout || 5000 ) - 200;
-
- function isCssImportApplied() {
- // Trigger reflow, repaint, redraw, whatever (cross-browser)
- var x = $element.css( 'height' );
- x = el.innerHTML;
- el.className = el.className;
- x = document.documentElement.clientHeight;
-
- return $element.css( prop ) === val;
- }
-
- function styleTestLoop() {
- var styleTestSince = new Date().getTime() - styleTestStart;
- // If it is passing or if we timed out, run the real test and stop the loop
- if ( isCssImportApplied() || styleTestSince > styleTestTimeout ) {
- assert.equal( $element.css( prop ), val,
- 'style "' + prop + ': ' + val + '" from url is applied (after ' + styleTestSince + 'ms)'
- );
-
- if ( fn ) {
- fn();
- }
-
- return;
- }
- // Otherwise, keep polling
- setTimeout( styleTestLoop );
- }
-
- // Start the loop
- styleTestStart = new Date().getTime();
- styleTestLoop();
- }
-
- function urlStyleTest( selector, prop, val ) {
- return QUnit.fixurl(
- mw.config.get( 'wgScriptPath' ) +
- '/tests/qunit/data/styleTest.css.php?' +
- $.param( {
- selector: selector,
- prop: prop,
- val: val
- } )
- );
- }
-
- QUnit.test( 'mw.loader', 2, function ( assert ) {
- var isAwesomeDone;
-
- mw.loader.testCallback = function () {
- assert.strictEqual( isAwesomeDone, undefined, 'Implementing module is.awesome: isAwesomeDone should still be undefined' );
- isAwesomeDone = true;
- };
-
- mw.loader.implement( 'test.callback', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' ) ] );
-
- return mw.loader.using( 'test.callback', function () {
- assert.strictEqual( isAwesomeDone, true, 'test.callback module should\'ve caused isAwesomeDone to be true' );
- delete mw.loader.testCallback;
-
- }, function () {
- assert.ok( false, 'Error callback fired while loader.using "test.callback" module' );
- } );
- } );
-
- QUnit.test( 'mw.loader with Object method as module name', 2, function ( assert ) {
- var isAwesomeDone;
-
- mw.loader.testCallback = function () {
- assert.strictEqual( isAwesomeDone, undefined, 'Implementing module hasOwnProperty: isAwesomeDone should still be undefined' );
- isAwesomeDone = true;
- };
-
- mw.loader.implement( 'hasOwnProperty', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' ) ], {}, {} );
-
- return mw.loader.using( 'hasOwnProperty', function () {
- assert.strictEqual( isAwesomeDone, true, 'hasOwnProperty module should\'ve caused isAwesomeDone to be true' );
- delete mw.loader.testCallback;
-
- }, function () {
- assert.ok( false, 'Error callback fired while loader.using "hasOwnProperty" module' );
- } );
- } );
-
- QUnit.test( 'mw.loader.using( .. ) Promise', 2, function ( assert ) {
- var isAwesomeDone;
-
- mw.loader.testCallback = function () {
- assert.strictEqual( isAwesomeDone, undefined, 'Implementing module is.awesome: isAwesomeDone should still be undefined' );
- isAwesomeDone = true;
- };
-
- mw.loader.implement( 'test.promise', [ QUnit.fixurl( mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/callMwLoaderTestCallback.js' ) ] );
-
- return mw.loader.using( 'test.promise' )
- .done( function () {
- assert.strictEqual( isAwesomeDone, true, 'test.promise module should\'ve caused isAwesomeDone to be true' );
- delete mw.loader.testCallback;
-
- } )
- .fail( function () {
- assert.ok( false, 'Error callback fired while loader.using "test.promise" module' );
- } );
- } );
-
- QUnit.test( 'mw.loader.implement( styles={ "css": [text, ..] } )', 2, function ( assert ) {
- var $element = $( '<div class="mw-test-implement-a"></div>' ).appendTo( '#qunit-fixture' );
-
- assert.notEqual(
- $element.css( 'float' ),
- 'right',
- 'style is clear'
- );
-
- mw.loader.implement(
- 'test.implement.a',
- function () {
- assert.equal(
- $element.css( 'float' ),
- 'right',
- 'style is applied'
- );
- },
- {
- all: '.mw-test-implement-a { float: right; }'
- }
- );
-
- return mw.loader.using( 'test.implement.a' );
- } );
-
- QUnit.test( 'mw.loader.implement( styles={ "url": { <media>: [url, ..] } } )', 7, function ( assert ) {
- var $element1 = $( '<div class="mw-test-implement-b1"></div>' ).appendTo( '#qunit-fixture' ),
- $element2 = $( '<div class="mw-test-implement-b2"></div>' ).appendTo( '#qunit-fixture' ),
- $element3 = $( '<div class="mw-test-implement-b3"></div>' ).appendTo( '#qunit-fixture' ),
- done = assert.async();
-
- assert.notEqual(
- $element1.css( 'text-align' ),
- 'center',
- 'style is clear'
- );
- assert.notEqual(
- $element2.css( 'float' ),
- 'left',
- 'style is clear'
- );
- assert.notEqual(
- $element3.css( 'text-align' ),
- 'right',
- 'style is clear'
- );
-
- mw.loader.implement(
- 'test.implement.b',
- function () {
- // Note: done() must only be called when the entire test is
- // complete. So, make sure that we don't start until *both*
- // assertStyleAsync calls have completed.
- var pending = 2;
- assertStyleAsync( assert, $element2, 'float', 'left', function () {
- assert.notEqual( $element1.css( 'text-align' ), 'center', 'print style is not applied' );
-
- pending--;
- if ( pending === 0 ) {
- done();
- }
- } );
- assertStyleAsync( assert, $element3, 'float', 'right', function () {
- assert.notEqual( $element1.css( 'text-align' ), 'center', 'print style is not applied' );
-
- pending--;
- if ( pending === 0 ) {
- done();
- }
- } );
- },
- {
- url: {
- print: [ urlStyleTest( '.mw-test-implement-b1', 'text-align', 'center' ) ],
- screen: [
- // bug 40834: Make sure it actually works with more than 1 stylesheet reference
- urlStyleTest( '.mw-test-implement-b2', 'float', 'left' ),
- urlStyleTest( '.mw-test-implement-b3', 'float', 'right' )
- ]
- }
- }
- );
-
- mw.loader.load( 'test.implement.b' );
- } );
-
- // Backwards compatibility
- QUnit.test( 'mw.loader.implement( styles={ <media>: text } ) (back-compat)', 2, function ( assert ) {
- var $element = $( '<div class="mw-test-implement-c"></div>' ).appendTo( '#qunit-fixture' );
-
- assert.notEqual(
- $element.css( 'float' ),
- 'right',
- 'style is clear'
- );
-
- mw.loader.implement(
- 'test.implement.c',
- function () {
- assert.equal(
- $element.css( 'float' ),
- 'right',
- 'style is applied'
- );
- },
- {
- all: '.mw-test-implement-c { float: right; }'
- }
- );
-
- return mw.loader.using( 'test.implement.c' );
- } );
-
- // Backwards compatibility
- QUnit.test( 'mw.loader.implement( styles={ <media>: [url, ..] } ) (back-compat)', 4, function ( assert ) {
- var $element = $( '<div class="mw-test-implement-d"></div>' ).appendTo( '#qunit-fixture' ),
- $element2 = $( '<div class="mw-test-implement-d2"></div>' ).appendTo( '#qunit-fixture' ),
- done = assert.async();
-
- assert.notEqual(
- $element.css( 'float' ),
- 'right',
- 'style is clear'
- );
- assert.notEqual(
- $element2.css( 'text-align' ),
- 'center',
- 'style is clear'
- );
-
- mw.loader.implement(
- 'test.implement.d',
- function () {
- assertStyleAsync( assert, $element, 'float', 'right', function () {
- assert.notEqual( $element2.css( 'text-align' ), 'center', 'print style is not applied (bug 40500)' );
- done();
- } );
- },
- {
- all: [ urlStyleTest( '.mw-test-implement-d', 'float', 'right' ) ],
- print: [ urlStyleTest( '.mw-test-implement-d2', 'text-align', 'center' ) ]
- }
- );
-
- mw.loader.load( 'test.implement.d' );
- } );
-
- // @import (bug 31676)
- QUnit.test( 'mw.loader.implement( styles has @import )', 7, function ( assert ) {
- var isJsExecuted, $element,
- done = assert.async();
-
- mw.loader.implement(
- 'test.implement.import',
- function () {
- assert.strictEqual( isJsExecuted, undefined, 'script not executed multiple times' );
- isJsExecuted = true;
-
- assert.equal( mw.loader.getState( 'test.implement.import' ), 'executing', 'module state during implement() script execution' );
-
- $element = $( '<div class="mw-test-implement-import">Foo bar</div>' ).appendTo( '#qunit-fixture' );
-
- assert.equal( mw.msg( 'test-foobar' ), 'Hello Foobar, $1!', 'messages load before script execution' );
-
- assertStyleAsync( assert, $element, 'float', 'right', function () {
- assert.equal( $element.css( 'text-align' ), 'center',
- 'CSS styles after the @import rule are working'
- );
-
- done();
- } );
- },
- {
- css: [
- '@import url(\''
- + urlStyleTest( '.mw-test-implement-import', 'float', 'right' )
- + '\');\n'
- + '.mw-test-implement-import { text-align: center; }'
- ]
- },
- {
- 'test-foobar': 'Hello Foobar, $1!'
- }
- );
-
- mw.loader.using( 'test.implement.import' ).always( function () {
- assert.strictEqual( isJsExecuted, true, 'script executed' );
- assert.equal( mw.loader.getState( 'test.implement.import' ), 'ready', 'module state after script execution' );
- } );
- } );
-
- QUnit.test( 'mw.loader.implement( dependency with styles )', 4, function ( assert ) {
- var $element = $( '<div class="mw-test-implement-e"></div>' ).appendTo( '#qunit-fixture' ),
- $element2 = $( '<div class="mw-test-implement-e2"></div>' ).appendTo( '#qunit-fixture' );
-
- assert.notEqual(
- $element.css( 'float' ),
- 'right',
- 'style is clear'
- );
- assert.notEqual(
- $element2.css( 'float' ),
- 'left',
- 'style is clear'
- );
-
- mw.loader.register( [
- [ 'test.implement.e', '0', [ 'test.implement.e2' ] ],
- [ 'test.implement.e2', '0' ]
- ] );
-
- mw.loader.implement(
- 'test.implement.e',
- function () {
- assert.equal(
- $element.css( 'float' ),
- 'right',
- 'Depending module\'s style is applied'
- );
- },
- {
- all: '.mw-test-implement-e { float: right; }'
- }
- );
-
- mw.loader.implement(
- 'test.implement.e2',
- function () {
- assert.equal(
- $element2.css( 'float' ),
- 'left',
- 'Dependency\'s style is applied'
- );
- },
- {
- all: '.mw-test-implement-e2 { float: left; }'
- }
- );
-
- return mw.loader.using( 'test.implement.e' );
- } );
-
- QUnit.test( 'mw.loader.implement( only scripts )', 1, function ( assert ) {
- mw.loader.implement( 'test.onlyscripts', function () {} );
- assert.strictEqual( mw.loader.getState( 'test.onlyscripts' ), 'ready' );
- } );
-
- QUnit.test( 'mw.loader.implement( only messages )', 2, function ( assert ) {
- assert.assertFalse( mw.messages.exists( 'bug_29107' ), 'Verify that the test message doesn\'t exist yet' );
-
- // jscs: disable requireCamelCaseOrUpperCaseIdentifiers
- mw.loader.implement( 'test.implement.msgs', [], {}, { bug_29107: 'loaded' } );
- // jscs: enable requireCamelCaseOrUpperCaseIdentifiers
-
- return mw.loader.using( 'test.implement.msgs', function () {
- assert.ok( mw.messages.exists( 'bug_29107' ), 'Bug 29107: messages-only module should implement ok' );
- }, function () {
- assert.ok( false, 'Error callback fired while implementing "test.implement.msgs" module' );
- } );
- } );
-
- QUnit.test( 'mw.loader.implement( empty )', 1, function ( assert ) {
- mw.loader.implement( 'test.empty' );
- assert.strictEqual( mw.loader.getState( 'test.empty' ), 'ready' );
- } );
-
- QUnit.test( 'mw.loader with broken indirect dependency', 4, function ( assert ) {
- // don't emit an error event
- this.sandbox.stub( mw, 'track' );
-
- mw.loader.register( [
- [ 'test.module1', '0' ],
- [ 'test.module2', '0', [ 'test.module1' ] ],
- [ 'test.module3', '0', [ 'test.module2' ] ]
- ] );
- mw.loader.implement( 'test.module1', function () {
- throw new Error( 'expected' );
- }, {}, {} );
- assert.strictEqual( mw.loader.getState( 'test.module1' ), 'error', 'Expected "error" state for test.module1' );
- assert.strictEqual( mw.loader.getState( 'test.module2' ), 'error', 'Expected "error" state for test.module2' );
- assert.strictEqual( mw.loader.getState( 'test.module3' ), 'error', 'Expected "error" state for test.module3' );
-
- assert.strictEqual( mw.track.callCount, 1 );
- } );
-
- QUnit.test( 'mw.loader with circular dependency', 1, function ( assert ) {
- mw.loader.register( [
- [ 'test.circle1', '0', [ 'test.circle2' ] ],
- [ 'test.circle2', '0', [ 'test.circle3' ] ],
- [ 'test.circle3', '0', [ 'test.circle1' ] ]
- ] );
- assert.throws( function () {
- mw.loader.using( 'test.circle3' );
- }, /Circular/, 'Detect circular dependency' );
- } );
-
- QUnit.test( 'mw.loader out-of-order implementation', 9, function ( assert ) {
- mw.loader.register( [
- [ 'test.module4', '0' ],
- [ 'test.module5', '0', [ 'test.module4' ] ],
- [ 'test.module6', '0', [ 'test.module5' ] ]
- ] );
- mw.loader.implement( 'test.module4', function () {} );
- assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
- assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' );
- assert.strictEqual( mw.loader.getState( 'test.module6' ), 'registered', 'Expected "registered" state for test.module6' );
- mw.loader.implement( 'test.module6', function () {} );
- assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
- assert.strictEqual( mw.loader.getState( 'test.module5' ), 'registered', 'Expected "registered" state for test.module5' );
- assert.strictEqual( mw.loader.getState( 'test.module6' ), 'loaded', 'Expected "loaded" state for test.module6' );
- mw.loader.implement( 'test.module5', function () {} );
- assert.strictEqual( mw.loader.getState( 'test.module4' ), 'ready', 'Expected "ready" state for test.module4' );
- assert.strictEqual( mw.loader.getState( 'test.module5' ), 'ready', 'Expected "ready" state for test.module5' );
- assert.strictEqual( mw.loader.getState( 'test.module6' ), 'ready', 'Expected "ready" state for test.module6' );
- } );
-
- QUnit.test( 'mw.loader missing dependency', 13, function ( assert ) {
- mw.loader.register( [
- [ 'test.module7', '0' ],
- [ 'test.module8', '0', [ 'test.module7' ] ],
- [ 'test.module9', '0', [ 'test.module8' ] ]
- ] );
- mw.loader.implement( 'test.module8', function () {} );
- assert.strictEqual( mw.loader.getState( 'test.module7' ), 'registered', 'Expected "registered" state for test.module7' );
- assert.strictEqual( mw.loader.getState( 'test.module8' ), 'loaded', 'Expected "loaded" state for test.module8' );
- assert.strictEqual( mw.loader.getState( 'test.module9' ), 'registered', 'Expected "registered" state for test.module9' );
- mw.loader.state( 'test.module7', 'missing' );
- assert.strictEqual( mw.loader.getState( 'test.module7' ), 'missing', 'Expected "missing" state for test.module7' );
- assert.strictEqual( mw.loader.getState( 'test.module8' ), 'error', 'Expected "error" state for test.module8' );
- assert.strictEqual( mw.loader.getState( 'test.module9' ), 'error', 'Expected "error" state for test.module9' );
- mw.loader.implement( 'test.module9', function () {} );
- assert.strictEqual( mw.loader.getState( 'test.module7' ), 'missing', 'Expected "missing" state for test.module7' );
- assert.strictEqual( mw.loader.getState( 'test.module8' ), 'error', 'Expected "error" state for test.module8' );
- assert.strictEqual( mw.loader.getState( 'test.module9' ), 'error', 'Expected "error" state for test.module9' );
- mw.loader.using(
- [ 'test.module7' ],
- function () {
- assert.ok( false, 'Success fired despite missing dependency' );
- assert.ok( true, 'QUnit expected() count dummy' );
- },
- function ( e, dependencies ) {
- assert.strictEqual( $.isArray( dependencies ), true, 'Expected array of dependencies' );
- assert.deepEqual( dependencies, [ 'test.module7' ], 'Error callback called with module test.module7' );
- }
- );
- mw.loader.using(
- [ 'test.module9' ],
- function () {
- assert.ok( false, 'Success fired despite missing dependency' );
- assert.ok( true, 'QUnit expected() count dummy' );
- },
- function ( e, dependencies ) {
- assert.strictEqual( $.isArray( dependencies ), true, 'Expected array of dependencies' );
- dependencies.sort();
- assert.deepEqual(
- dependencies,
- [ 'test.module7', 'test.module8', 'test.module9' ],
- 'Error callback called with all three modules as dependencies'
- );
- }
- );
- } );
-
- QUnit.test( 'mw.loader dependency handling', 5, function ( assert ) {
- var done = assert.async();
- mw.loader.register( [
- // [module, version, dependencies, group, source]
- [ 'testMissing', '1', [], null, 'testloader' ],
- [ 'testUsesMissing', '1', [ 'testMissing' ], null, 'testloader' ],
- [ 'testUsesNestedMissing', '1', [ 'testUsesMissing' ], null, 'testloader' ]
- ] );
-
- function verifyModuleStates() {
- assert.equal( mw.loader.getState( 'testMissing' ), 'missing', 'Module not known to server must have state "missing"' );
- assert.equal( mw.loader.getState( 'testUsesMissing' ), 'error', 'Module with missing dependency must have state "error"' );
- assert.equal( mw.loader.getState( 'testUsesNestedMissing' ), 'error', 'Module with indirect missing dependency must have state "error"' );
- }
-
- mw.loader.using( [ 'testUsesNestedMissing' ],
- function () {
- assert.ok( false, 'Error handler should be invoked.' );
- assert.ok( true ); // Dummy to reach QUnit expect()
-
- verifyModuleStates();
-
- done();
- },
- function ( e, badmodules ) {
- assert.ok( true, 'Error handler should be invoked.' );
- // As soon as server spits out state('testMissing', 'missing');
- // it will bubble up and trigger the error callback.
- // Therefor the badmodules array is not testUsesMissing or testUsesNestedMissing.
- assert.deepEqual( badmodules, [ 'testMissing' ], 'Bad modules as expected.' );
-
- verifyModuleStates();
-
- done();
- }
- );
- } );
-
- QUnit.test( 'mw.loader skin-function handling', 5, function ( assert ) {
- mw.loader.register( [
- // [module, version, dependencies, group, source, skip]
- [ 'testSkipped', '1', [], null, 'testloader', 'return true;' ],
- [ 'testNotSkipped', '1', [], null, 'testloader', 'return false;' ],
- [ 'testUsesSkippable', '1', [ 'testSkipped', 'testNotSkipped' ], null, 'testloader' ]
- ] );
-
- function verifyModuleStates() {
- assert.equal( mw.loader.getState( 'testSkipped' ), 'ready', 'Module is ready when skipped' );
- assert.equal( mw.loader.getState( 'testNotSkipped' ), 'ready', 'Module is ready when not skipped but loaded' );
- assert.equal( mw.loader.getState( 'testUsesSkippable' ), 'ready', 'Module is ready when skippable dependencies are ready' );
- }
-
- return mw.loader.using( [ 'testUsesSkippable' ],
- function () {
- assert.ok( true, 'Success handler should be invoked.' );
- assert.ok( true ); // Dummy to match error handler and reach QUnit expect()
-
- verifyModuleStates();
- },
- function ( e, badmodules ) {
- assert.ok( false, 'Error handler should not be invoked.' );
- assert.deepEqual( badmodules, [], 'Bad modules as expected.' );
-
- verifyModuleStates();
- }
- );
- } );
-
- QUnit.asyncTest( 'mw.loader( "//protocol-relative" ) (bug 30825)', 2, function ( assert ) {
- // This bug was actually already fixed in 1.18 and later when discovered in 1.17.
- // Test is for regressions!
-
- // Forge a URL to the test callback script
- var target = QUnit.fixurl(
- mw.config.get( 'wgServer' ) + mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/qunitOkCall.js'
- );
-
- // Confirm that mw.loader.load() works with protocol-relative URLs
- target = target.replace( /https?:/, '' );
-
- assert.equal( target.slice( 0, 2 ), '//',
- 'URL must be relative to test relative URLs!'
- );
-
- // Async!
- // The target calls QUnit.start
- mw.loader.load( target );
- } );
-
- QUnit.asyncTest( 'mw.loader( "/absolute-path" )', 2, function ( assert ) {
- // Forge a URL to the test callback script
- var target = QUnit.fixurl(
- mw.config.get( 'wgScriptPath' ) + '/tests/qunit/data/qunitOkCall.js'
- );
-
- // Confirm that mw.loader.load() works with absolute-paths (relative to current hostname)
- assert.equal( target.slice( 0, 1 ), '/', 'URL is relative to document root' );
-
- // Async!
- // The target calls QUnit.start
- mw.loader.load( target );
- } );
-
- QUnit.test( 'mw.loader() executing race (T112232)', 2, function ( assert ) {
- var done = false;
-
- // The red herring schedules its CSS buffer first. In T112232, a bug in the
- // state machine would cause the job for testRaceLoadMe to run with an earlier job.
- mw.loader.implement(
- 'testRaceRedHerring',
- function () {},
- { css: [ '.mw-testRaceRedHerring {}' ] }
- );
- mw.loader.implement(
- 'testRaceLoadMe',
- function () {
- done = true;
- },
- { css: [ '.mw-testRaceLoadMe { float: left; }' ] }
- );
-
- mw.loader.load( [ 'testRaceRedHerring', 'testRaceLoadMe' ] );
- return mw.loader.using( 'testRaceLoadMe', function () {
- assert.strictEqual( done, true, 'script ran' );
- assert.strictEqual( mw.loader.getState( 'testRaceLoadMe' ), 'ready', 'state' );
- } );
- } );
-
QUnit.test( 'mw.hook', 13, function ( assert ) {
var hook, add, fire, chars, callback;
);
} );
- QUnit.test( 'mw.loader require()', 6, function ( assert ) {
- mw.loader.register( [
- [ 'test.require1', '0' ],
- [ 'test.require2', '0' ],
- [ 'test.require3', '0' ],
- [ 'test.require4', '0', [ 'test.require3' ] ]
- ] );
- mw.loader.implement( 'test.require1', function () {} );
- mw.loader.implement( 'test.require2', function ( $, jQuery, require, module ) {
- module.exports = 1;
- } );
- mw.loader.implement( 'test.require3', function ( $, jQuery, require, module ) {
- module.exports = function () {
- return 'hello world';
- };
- } );
- mw.loader.implement( 'test.require4', function ( $, jQuery, require, module ) {
- var other = require( 'test.require3' );
- module.exports = {
- pizza: function () {
- return other();
- }
- };
- } );
- return mw.loader.using( [ 'test.require1', 'test.require2', 'test.require3', 'test.require4' ] )
- .then( function ( require ) {
- var module1, module2, module3, module4;
-
- module1 = require( 'test.require1' );
- module2 = require( 'test.require2' );
- module3 = require( 'test.require3' );
- module4 = require( 'test.require4' );
-
- assert.strictEqual( typeof module1, 'object', 'export of module with no export' );
- assert.strictEqual( module2, 1, 'export a number' );
- assert.strictEqual( module3(), 'hello world', 'export a function' );
- assert.strictEqual( typeof module4.pizza, 'function', 'export an object' );
- assert.strictEqual( module4.pizza(), 'hello world', 'module can require other modules' );
-
- assert.throws( function () {
- require( '_badmodule' );
- }, /is not loaded/, 'Requesting non-existent modules throws error.' );
- } );
- } );
-
- QUnit.test( 'mw.loader require() in debug mode', 1, function ( assert ) {
- var path = mw.config.get( 'wgScriptPath' );
- mw.loader.register( [
- [ 'test.require.define', '0' ],
- [ 'test.require.callback', '0', [ 'test.require.define' ] ]
- ] );
- mw.loader.implement( 'test.require.callback', [ QUnit.fixurl( path + '/tests/qunit/data/requireCallMwLoaderTestCallback.js' ) ] );
- mw.loader.implement( 'test.require.define', [ QUnit.fixurl( path + '/tests/qunit/data/defineCallMwLoaderTestCallback.js' ) ] );
-
- return mw.loader.using( 'test.require.callback' ).then( function ( require ) {
- var exported = require( 'test.require.callback' );
- assert.strictEqual( exported, 'Require worked.Define worked.',
- 'module.exports worked in debug mode' );
- }, function () {
- assert.ok( false, 'Error callback fired while loader.using "test.require.callback" module' );
- } );
- } );
-
-}( mediaWiki, jQuery ) );
+}( mediaWiki ) );