use the 'rawcontinue' parameter to receive raw query-continue data, but the
new style is encouraged as it's harder to implement incorrectly.
* Deprecated API formats dump and wddx have been completely removed.
+* (T7645) The "Signature" button on the edit toolbar is now hidden by default
+ in non-talk namespaces. A new configuration variable,
+ $wgExtraSignatureNamespaces, controls in which subject (non-talk) namespaces
+ the "Signature" button on the edit toolbar will be displayed.
* $wgResourceLoaderUseESI was deprecated and removed. This was an experimental
feature that was never enabled by default.
* SpecialPageFactory::setGroup was removed (deprecated in 1.21).
* SpecialPageFactory::getGroup was removed (deprecated in 1.21).
* DatabaseBase::ignoreErrors() is now protected.
+* BREAKING CHANGE: mediawiki.legacy.ajax has been removed, following
+ a lengthy deprecation period.
== Compatibility ==
*/
$wgContentNamespaces = array( NS_MAIN );
+/**
+ * Array of namespaces, in addition to the talk namespaces, where signatures
+ * (~~~~) are likely to be used. This determines whether to display the
+ * Signature button on the edit toolbar, and may also be used by extensions.
+ * For example, "traditional" style wikis, where content and discussion are
+ * intermixed, could place NS_MAIN and NS_PROJECT namespaces in this array.
+ */
+$wgExtraSignatureNamespaces = array();
+
/**
* Max number of redirects to follow when resolving redirects.
* 1 means only the first redirect is followed (default behavior).
$wgOut->addHTML( $this->editFormTextBeforeContent );
if ( !$this->isCssJsSubpage && $showToolbar && $wgUser->getOption( 'showtoolbar' ) ) {
- $wgOut->addHTML( EditPage::getEditToolbar() );
+ $wgOut->addHTML( EditPage::getEditToolbar( $this->mTitle ) );
}
if ( $this->blankArticle ) {
* Shows a bulletin board style toolbar for common editing functions.
* It can be disabled in the user preferences.
*
+ * @param $title Title object for the page being edited (optional)
* @return string
*/
- static function getEditToolbar() {
+ static function getEditToolbar( $title = null ) {
global $wgContLang, $wgOut;
global $wgEnableUploads, $wgForeignFileRepos;
$imagesAvailable = $wgEnableUploads || count( $wgForeignFileRepos );
+ $showSignature = true;
+ if ( $title ) {
+ $showSignature = MWNamespace::wantSignatures( $title->getNamespace() );
+ }
/**
* $toolarray is an array of arrays each of which includes the
'sample' => wfMessage( 'nowiki_sample' )->text(),
'tip' => wfMessage( 'nowiki_tip' )->text(),
),
- array(
+ $showSignature ? array(
'id' => 'mw-editbutton-signature',
'open' => '--~~~~',
'close' => '',
'sample' => '',
'tip' => wfMessage( 'sig_tip' )->text(),
- ),
+ ) : false,
array(
'id' => 'mw-editbutton-hr',
'open' => "\n----\n",
return $index == NS_MAIN || in_array( $index, $wgContentNamespaces );
}
+ /**
+ * Might pages in this namespace require the use of the Signature button on
+ * the edit toolbar?
+ *
+ * @param int $index Index to check
+ * @return bool
+ */
+ public static function wantSignatures( $index ) {
+ global $wgExtraSignatureNamespaces;
+ return self::isTalk( $index ) || in_array( $index, $wgExtraSignatureNamespaces );
+ }
+
/**
* Can pages in a namespace be watched?
*
}
$ret .= $this->buildCssLinks() . "\n";
-
$ret .= $this->getHeadScripts() . "\n";
foreach ( $this->mHeadItems as $item ) {
}
/**
- * @todo Document
+ * Construct neccecary html and loader preset states to load modules on a page.
+ *
+ * Use getHtmlFromLoaderLinks() to convert this array to HTML.
+ *
* @param array|string $modules One or more module names
* @param string $only ResourceLoaderModule TYPE_ class constant
- * @param array $extraQuery Array with extra query parameters to add to each
- * request. array( param => value ).
- * @param bool $loadCall If true, output an (asynchronous) mw.loader.load()
- * call rather than a "<script src='...'>" tag.
- * @return string The html "<script>", "<link>" and "<style>" tags
- */
- public function makeResourceLoaderLink( $modules, $only, array $extraQuery = array(),
- $loadCall = false
- ) {
+ * @param array $extraQuery [optional] Array with extra query parameters for the request
+ * @return array A list of HTML strings and array of client loader preset states
+ */
+ public function makeResourceLoaderLink( $modules, $only, array $extraQuery = array() ) {
$modules = (array)$modules;
$links = array(
if ( ResourceLoader::inDebugMode() ) {
// Recursively call us for every item
foreach ( $modules as $name ) {
- $link = $this->makeResourceLoaderLink( $name, $only );
+ $link = $this->makeResourceLoaderLink( $name, $only, $extraQuery );
$links['html'] = array_merge( $links['html'], $link['html'] );
$links['states'] += $link['states'];
}
// Automatically select style/script elements
if ( $only === ResourceLoaderModule::TYPE_STYLES ) {
$link = Html::linkedStyle( $url );
- } elseif ( $loadCall ) {
- $link = ResourceLoader::makeInlineScript(
- Xml::encodeJsCall( 'mw.loader.load', array( $url, 'text/javascript', true ) )
- );
} else {
- $link = Html::linkedScript( $url );
- if ( !$context->getRaw() && !$isRaw ) {
- // Wrap only=script / only=combined requests in a conditional as
- // browsers not supported by the startup module would unconditionally
- // execute this module. Otherwise users will get "ReferenceError: mw is
- // undefined" or "jQuery is undefined" from e.g. a "site" module.
+ if ( $context->getRaw() || $isRaw ) {
+ // Startup module can't load itself, needs to use <script> instead of mw.loader.load
+ $link = Html::element( 'script', array(
+ // In SpecialJavaScriptTest, QUnit must load synchronous
+ 'async' => !isset( $extraQuery['sync'] ),
+ 'src' => $url
+ ) );
+ } else {
$link = ResourceLoader::makeInlineScript(
- Xml::encodeJsCall( 'document.write', array( $link ) )
+ Xml::encodeJsCall( 'mw.loader.load', array( $url ) )
);
}
-
- // For modules requested directly in the html via <link> or <script>,
- // tell mw.loader they are being loading to prevent duplicate requests.
- foreach ( $grpModules as $key => $module ) {
- // Don't output state=loading for the startup module..
- if ( $key !== 'startup' ) {
- $links['states'][$key] = 'loading';
- }
- }
}
if ( $group == 'noscript' ) {
* @return string HTML fragment
*/
function getHeadScripts() {
- // Startup - this will immediately load jquery and mediawiki modules
$links = array();
+
+ // Client profile classes for <html>. Allows for easy hiding/showing of UI components.
+ // Must be done synchronously on every page to avoid flashes of wrong content.
+ // Note: This class distinguishes MediaWiki-supported JavaScript from the rest.
+ // The "rest" includes browsers that support JavaScript but not supported by our runtime.
+ // For the performance benefit of the majority, this is added unconditionally here and is
+ // then fixed up by the startup module for unsupported browsers.
+ $links[] = Html::inlineScript(
+ 'document.documentElement.className = document.documentElement.className'
+ . '.replace( /(^|\s)client-nojs(\s|$)/, "$1client-js$2" );'
+ );
+
+ // Startup - this provides the client with the module manifest and loads jquery and mediawiki base modules
$links[] = $this->makeResourceLoaderLink( 'startup', ResourceLoaderModule::TYPE_SCRIPTS );
// Load config before anything else
);
if ( $this->getConfig()->get( 'ResourceLoaderExperimentalAsyncLoading' ) ) {
- $links[] = $this->getScriptsForBottomQueue( true );
+ $links[] = $this->getScriptsForBottomQueue();
}
return self::getHtmlFromLoaderLinks( $links );
* 'bottom', legacy scripts ($this->mScripts), user preferences, site JS
* and user JS.
*
- * @param bool $inHead If true, this HTML goes into the "<head>",
- * if false it goes into the "<body>".
+ * @param bool $unused Previously used to let this method change its output based
+ * on whether it was called by getHeadScripts() or getBottomScripts().
* @return string
*/
- function getScriptsForBottomQueue( $inHead ) {
+ function getScriptsForBottomQueue( $unused = null ) {
// Scripts "only" requests marked for bottom inclusion
// If we're in the <head>, use load() calls rather than <script src="..."> tags
$links = array();
$links[] = $this->makeResourceLoaderLink( $this->getModuleScripts( true, 'bottom' ),
- ResourceLoaderModule::TYPE_SCRIPTS, /* $extraQuery = */ array(),
- /* $loadCall = */ $inHead
+ ResourceLoaderModule::TYPE_SCRIPTS
);
$links[] = $this->makeResourceLoaderLink( $this->getModuleStyles( true, 'bottom' ),
- ResourceLoaderModule::TYPE_STYLES, /* $extraQuery = */ array(),
- /* $loadCall = */ $inHead
+ ResourceLoaderModule::TYPE_STYLES
);
// Modules requests - let the client calculate dependencies and batch requests as it likes
$modules = $this->getModules( true, 'bottom' );
if ( $modules ) {
$links[] = ResourceLoader::makeInlineScript(
- Xml::encodeJsCall( 'mw.loader.load', array( $modules, null, true ) )
+ Xml::encodeJsCall( 'mw.loader.load', array( $modules ) )
);
}
// We're on a preview of a JS subpage. Exclude this page from the user module (T28283)
// and include the draft contents as a raw script instead.
$links[] = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_COMBINED,
- array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() ), $inHead
+ array( 'excludepage' => $this->getTitle()->getPrefixedDBkey() )
);
// Load the previewed JS
$links[] = ResourceLoader::makeInlineScript(
// the excluded subpage.
} else {
// Include the user module normally, i.e., raw to avoid it being wrapped in a closure.
- $links[] = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_COMBINED,
- /* $extraQuery = */ array(), /* $loadCall = */ $inHead
- );
+ $links[] = $this->makeResourceLoaderLink( 'user', ResourceLoaderModule::TYPE_COMBINED );
}
// Group JS is only enabled if site JS is enabled.
- $links[] = $this->makeResourceLoaderLink( 'user.groups', ResourceLoaderModule::TYPE_COMBINED,
- /* $extraQuery = */ array(), /* $loadCall = */ $inHead
- );
+ $links[] = $this->makeResourceLoaderLink( 'user.groups', ResourceLoaderModule::TYPE_COMBINED );
return self::getHtmlFromLoaderLinks( $links );
}
// In case the skin wants to add bottom CSS
$this->getSkin()->setupSkinUserCss( $this );
- // Optimise jQuery ready event cross-browser.
- // This also enforces $.isReady to be true at </body> which fixes the
- // mw.loader bug in Firefox with using document.write between </body>
- // and the DOMContentReady event (bug 47457).
- $html = Html::inlineScript( 'if(window.jQuery)jQuery.ready();' );
-
- if ( !$this->getConfig()->get( 'ResourceLoaderExperimentalAsyncLoading' ) ) {
- $html .= $this->getScriptsForBottomQueue( false );
+ if ( $this->getConfig()->get( 'ResourceLoaderExperimentalAsyncLoading' ) ) {
+ // Already handled by getHeadScripts()
+ return '';
}
-
- return $html;
+ return $this->getScriptsForBottomQueue();
}
/**
* If the code is invalid or has expired, returns NULL.
*
* @param string $code Confirmation code
+ * @param int $flags User::READ_* bitfield
* @return User|null
*/
- public static function newFromConfirmationCode( $code ) {
- $dbr = wfGetDB( DB_SLAVE );
- $id = $dbr->selectField( 'user', 'user_id', array(
- 'user_email_token' => md5( $code ),
- 'user_email_token_expires > ' . $dbr->addQuotes( $dbr->timestamp() ),
- ) );
- if ( $id !== false ) {
- return User::newFromId( $id );
- } else {
- return null;
- }
+ public static function newFromConfirmationCode( $code, $flags = 0 ) {
+ $db = ( $flags & self::READ_LATEST ) == self::READ_LATEST
+ ? wfGetDB( DB_MASTER )
+ : wfGetDB( DB_SLAVE );
+
+ $id = $db->selectField(
+ 'user',
+ 'user_id',
+ array(
+ 'user_email_token' => md5( $code ),
+ 'user_email_token_expires > ' . $db->addQuotes( $db->timestamp() ),
+ )
+ );
+
+ return $id ? User::newFromId( $id ) : null;
}
/**
}
}
- $eJob = new self( Title::newMainPage(), array( 'jobsByWiki' => $jobMapsByWiki ) );
+ $eJob = new self(
+ Title::makeTitle( NS_SPECIAL, 'Badtitle/' . __CLASS__ ),
+ array( 'jobsByWiki' => $jobMapsByWiki )
+ );
// If *all* jobs to be pushed are to be de-duplicated (a common case), then
// de-duplicate this whole job itself to avoid build up in high traffic cases
$eJob->removeDuplicates = $deduplicate;
* @ingroup Cache
*/
class APCBagOStuff extends BagOStuff {
-
/**
* @var string String to append to each APC key. This may be changed
* whenever the handling of values is changed, to prevent existing code
**/
const KEY_SUFFIX = ':1';
- public function get( $key, &$casToken = null ) {
+ public function get( $key, &$casToken = null, $flags = 0 ) {
$val = apc_fetch( $key . self::KEY_SUFFIX );
$casToken = $val;
const ERR_UNREACHABLE = 2; // can't connect
const ERR_UNEXPECTED = 3; // response gave some error
+ /** Bitfield constants for get()/getMulti() */
+ const READ_LATEST = 1; // use latest data for replicated stores
+
public function __construct( array $params = array() ) {
if ( isset( $params['logger'] ) ) {
$this->setLogger( $params['logger'] );
* Get an item with the given key. Returns false if it does not exist.
* @param string $key
* @param mixed $casToken [optional]
+ * @param integer $flags Bitfield; supports READ_LATEST [optional]
* @return mixed Returns false on failure
*/
- abstract public function get( $key, &$casToken = null );
+ abstract public function get( $key, &$casToken = null, $flags = 0 );
/**
* Set an item.
return false;
}
- /* *** Emulated functions *** */
-
/**
* Get an associative array containing the item for each of the keys that have items.
* @param array $keys List of strings
+ * @param integer $flags Bitfield; supports READ_LATEST [optional]
* @return array
*/
- public function getMulti( array $keys ) {
+ public function getMulti( array $keys, $flags = 0 ) {
$res = array();
foreach ( $keys as $key ) {
$val = $this->get( $key );
* @ingroup Cache
*/
class EmptyBagOStuff extends BagOStuff {
- public function get( $key, &$casToken = null ) {
+ public function get( $key, &$casToken = null, $flags = 0 ) {
return false;
}
return true;
}
- public function get( $key, &$casToken = null ) {
+ public function get( $key, &$casToken = null, $flags = 0 ) {
if ( !isset( $this->bag[$key] ) ) {
return false;
}
$this->readStore->setDebug( $debug );
}
- public function get( $key, &$casToken = null ) {
- return $this->readStore->get( $key, $casToken );
+ public function get( $key, &$casToken = null, $flags = 0 ) {
+ return ( $flags & self::READ_LATEST )
+ ? $this->writeStore->get( $key, $casToken, $flags )
+ : $this->readStore->get( $key, $casToken, $flags );
}
- public function getMulti( array $keys ) {
- return $this->readStore->getMulti( $keys );
+ public function getMulti( array $keys, $flags = 0 ) {
+ return ( $flags & self::READ_LATEST )
+ ? $this->writeStore->getMulti( $keys, $flags )
+ : $this->readStore->getMulti( $keys, $flags );
}
public function set( $key, $value, $exptime = 0 ) {
* @ingroup Cache
*/
class WinCacheBagOStuff extends BagOStuff {
-
- /**
- * Get a value from the WinCache object cache
- *
- * @param string $key Cache key
- * @param int $casToken [optional] Cas token
- * @return mixed
- */
- public function get( $key, &$casToken = null ) {
+ public function get( $key, &$casToken = null, $flags = 0 ) {
$val = wincache_ucache_get( $key );
$casToken = $val;
return $val;
}
- /**
- * Store a value in the WinCache object cache
- *
- * @param string $key Cache key
- * @param mixed $value Value to store
- * @param int $expire Expiration time
- * @return bool
- */
public function set( $key, $value, $expire = 0 ) {
$result = wincache_ucache_set( $key, serialize( $value ), $expire );
return ( is_array( $result ) && $result === array() ) || $result;
}
- /**
- * Store a value in the WinCache object cache, race condition-safe
- *
- * @param int $casToken Cas token
- * @param string $key Cache key
- * @param int $value Object to store
- * @param int $exptime Expiration time
- * @return bool
- */
protected function cas( $casToken, $key, $value, $exptime = 0 ) {
return wincache_ucache_cas( $key, $casToken, serialize( $value ) );
}
- /**
- * Remove a value from the WinCache object cache
- *
- * @param string $key Cache key
- * @return bool
- */
public function delete( $key ) {
wincache_ucache_delete( $key );
* @ingroup Cache
*/
class XCacheBagOStuff extends BagOStuff {
- /**
- * Get a value from the XCache object cache
- *
- * @param string $key Cache key
- * @param mixed $casToken Cas token
- * @return mixed
- */
- public function get( $key, &$casToken = null ) {
+ public function get( $key, &$casToken = null, $flags = 0 ) {
$val = xcache_get( $key );
if ( is_string( $val ) ) {
return $val;
}
- /**
- * Store a value in the XCache object cache
- *
- * @param string $key Cache key
- * @param mixed $value Object to store
- * @param int $expire Expiration time
- * @return bool
- */
public function set( $key, $value, $expire = 0 ) {
if ( !$this->isInteger( $value ) ) {
$value = serialize( $value );
return true;
}
- /**
- * Remove a value from the XCache object cache
- *
- * @param string $key Cache key
- * @return bool
- */
public function delete( $key ) {
xcache_unset( $key );
return true;
return $params;
}
- /**
- * @param string $key
- * @param mixed $casToken [optional]
- * @return mixed
- */
- public function get( $key, &$casToken = null ) {
+ public function get( $key, &$casToken = null, $flags = 0 ) {
return $this->client->get( $this->encodeKey( $key ), $casToken );
}
$this->client->addServers( $servers );
}
- /**
- * @param string $key
- * @param float $casToken [optional]
- * @return mixed
- */
- public function get( $key, &$casToken = null ) {
+ public function get( $key, &$casToken = null, $flags = 0 ) {
$this->debugLog( "get($key)" );
$result = $this->client->get( $this->encodeKey( $key ), null, $casToken );
$result = $this->checkResult( $key, $result );
return $result;
}
- /**
- * @param array $keys
- * @return array
- */
- public function getMulti( array $keys ) {
+ public function getMulti( array $keys, $flags = 0 ) {
$this->debugLog( 'getMulti(' . implode( ', ', $keys ) . ')' );
$callback = array( $this, 'encodeKey' );
$result = $this->client->getMulti( array_map( $callback, $keys ) );
$this->client->set_debug( $debug );
}
- /**
- * @param array $keys
- * @return array
- */
- public function getMulti( array $keys ) {
+ public function getMulti( array $keys, $flags = 0 ) {
$callback = array( $this, 'encodeKey' );
return $this->client->get_multi( array_map( $callback, $keys ) );
}
$this->doWrite( 'setDebug', $debug );
}
- /**
- * @param string $key
- * @param mixed $casToken [optional]
- * @return bool|mixed
- */
- public function get( $key, &$casToken = null ) {
+ public function get( $key, &$casToken = null, $flags = 0 ) {
foreach ( $this->caches as $cache ) {
- $value = $cache->get( $key );
+ $value = $cache->get( $key, null, $flags = 0 );
if ( $value !== false ) {
return $value;
}
* The word "cache" has two main dictionary meanings, and both
* are used in this factory class. They are:
* - a) A place to store copies or computations on existing data
- * for higher access speeds (the computer science definition)
+ * for higher access speeds (the computer science definition)
* - b) A place to store lightweight data that is not canonically
- * stored anywhere else (e.g. a "hoard" of objects)
+ * stored anywhere else (e.g. a "hoard" of objects)
+ *
+ * The former should always use strongly consistent stores, so callers don't
+ * have to deal with stale reads. The later may be eventually consistent, but
+ * callers can use BagOStuff:READ_LATEST to see the latest available data.
*
* @ingroup Cache
*/
* In general, this means avoiding updates on idempotent HTTP requests and
* avoiding an assumption of perfect serializability (or accepting anomalies).
* Reads may be eventually consistent or data might rollback as nodes flap.
+ * Callers can use BagOStuff:READ_LATEST to see the latest available data.
*
- * @since 1.26
* @return BagOStuff
+ * @since 1.26
*/
static function getMainStashInstance() {
global $wgMainStash;
}
}
- public function get( $key, &$casToken = null ) {
+ public function get( $key, &$casToken = null, $flags = 0 ) {
list( $server, $conn ) = $this->getConnection( $key );
if ( !$conn ) {
return $result;
}
- public function getMulti( array $keys ) {
+ public function getMulti( array $keys, $flags = 0 ) {
$batches = array();
$conns = array();
}
}
- /**
- * @param string $key
- * @param mixed $casToken [optional]
- * @return mixed
- */
- public function get( $key, &$casToken = null ) {
+ public function get( $key, &$casToken = null, $flags = 0 ) {
$values = $this->getMulti( array( $key ) );
if ( array_key_exists( $key, $values ) ) {
$casToken = $values[$key];
return false;
}
- /**
- * @param array $keys
- * @return array
- */
- public function getMulti( array $keys ) {
+ public function getMulti( array $keys, $flags = 0 ) {
$values = array(); // array of (key => value)
$keysByTable = array();
* @return string
*/
public static function makeLoaderConditionalScript( $script ) {
- return "var RLQ = RLQ || []; RLQ.push( function () {\n" . trim( $script ) . "\n} );";
+ return "window.RLQ = window.RLQ || []; window.RLQ.push( function () {\n" . trim( $script ) . "\n} );";
}
/**
$js = self::makeLoaderConditionalScript( $script );
return new WrappedString(
Html::inlineScript( $js ),
- "<script>var RLQ = RLQ || []; RLQ.push( function () {\n",
+ "<script>window.RLQ = window.RLQ || []; window.RLQ.push( function () {\n",
"\n} );</script>"
);
}
'wgContentNamespaces' => MWNamespace::getContentNamespaces(),
'wgSiteName' => $conf->get( 'Sitename' ),
'wgDBname' => $conf->get( 'DBname' ),
+ 'wgExtraSignatureNamespaces' => $conf->get( 'ExtraSignatureNamespaces' ),
'wgAvailableSkins' => Skin::getSkinNames(),
'wgExtensionAssetsPath' => $conf->get( 'ExtensionAssetsPath' ),
// MediaWiki sets cookies to have this prefix by default
$resourceLoader = $context->getResourceLoader();
$target = $context->getRequest()->getVal( 'target', 'desktop' );
+ // Bypass target filter if this request is from a unit test context. To prevent misuse in
+ // production, this is only allowed if testing is enabled server-side.
+ $byPassTargetFilter = $this->getConfig()->get( 'EnableJavaScriptTest' ) && $target === 'test';
$out = '';
$registryData = array();
foreach ( $resourceLoader->getModuleNames() as $name ) {
$module = $resourceLoader->getModule( $name );
$moduleTargets = $module->getTargets();
- if ( !in_array( $target, $moduleTargets ) ) {
+ if ( !$byPassTargetFilter && !in_array( $target, $moduleTargets ) ) {
continue;
}
}, array(
'$VARS.wgLegacyJavaScriptGlobals' => $this->getConfig()->get( 'LegacyJavaScriptGlobals' ),
'$VARS.configuration' => $this->getConfigSettings( $context ),
- '$VARS.baseModulesScript' => Html::linkedScript( self::getStartupModulesUrl( $context ) ),
+ '$VARS.baseModulesUri' => self::getStartupModulesUrl( $context ),
) );
$pairs['$CODE.registrations()'] = str_replace( "\n", "\n\t", trim( $this->getModuleRegistrations( $context ) ) );
// Add various resources if required
if ( $wgUseAjax ) {
- $modules['legacy'][] = 'mediawiki.legacy.ajax';
-
if ( $wgEnableAPI ) {
if ( $wgEnableWriteAPI && $wgAjaxWatch && $user->isLoggedIn()
&& $user->isAllowed( 'writeapi' )
* @param string $code Confirmation code
*/
function attemptConfirm( $code ) {
- $user = User::newFromConfirmationCode( $code );
+ $user = User::newFromConfirmationCode( $code, User::READ_LATEST );
if ( !is_object( $user ) ) {
$this->getOutput()->addWikiMsg( 'confirmemail_invalid' );
* @param string $code Confirmation code
*/
function attemptInvalidate( $code ) {
- $user = User::newFromConfirmationCode( $code );
+ $user = User::newFromConfirmationCode( $code, User::READ_LATEST );
if ( !is_object( $user ) ) {
$this->getOutput()->addWikiMsg( 'confirmemail_invalid' );
'lang' => $this->getLanguage()->getCode(),
'skin' => $this->getSkin()->getSkinName(),
'debug' => ResourceLoader::inDebugMode() ? 'true' : 'false',
+ 'target' => 'test',
);
$embedContext = new ResourceLoaderContext( $rl, new FauxRequest( $query ) );
$query['only'] = 'scripts';
$startupContext = new ResourceLoaderContext( $rl, new FauxRequest( $query ) );
+ $query['raw'] = true;
+
$modules = $rl->getTestModuleNames( 'qunit' );
+ // Disable autostart because we load modules asynchronously. By default, QUnit would start
+ // at domready when there are no tests loaded and also fire 'QUnit.done' which then instructs
+ // Karma to end the run before the tests even started.
+ $qunitConfig = 'QUnit.config.autostart = false;'
+ . 'if (window.__karma__) {'
+ // karma-qunit's use of autostart=false and QUnit.start conflicts with ours.
+ // Hack around this by replacing 'karma.loaded' with a no-op and call it ourselves later.
+ // See <https://github.com/karma-runner/karma-qunit/issues/27>.
+ . 'window.__karma__.loaded = function () {};'
+ . '}';
+
// The below is essentially a pure-javascript version of OutputPage::getHeadScripts.
$startup = $rl->makeModuleResponse( $startupContext, array(
'startup' => $rl->getModule( 'startup' ),
'user.options' => $rl->getModule( 'user.options' ),
'user.tokens' => $rl->getModule( 'user.tokens' ),
) );
- $code .= Xml::encodeJsCall( 'mw.loader.load', array( $modules ) );
+ // Catch exceptions (such as "dependency missing" or "unknown module") so that we
+ // always start QUnit. Re-throw so that they are caught and reported as global exceptions
+ // by QUnit and Karma.
+ $code .= '(function () {'
+ . 'var start = window.__karma__ ? window.__karma__.start : QUnit.start;'
+ . 'try {'
+ . 'mw.loader.using( ' . Xml::encodeJsVar( $modules ) . ' ).always( start );'
+ . '} catch ( e ) { start(); throw e; }'
+ . '}());';
header( 'Content-Type: text/javascript; charset=utf-8' );
header( 'Cache-Control: private, no-cache, must-revalidate' );
header( 'Pragma: no-cache' );
+ echo $qunitConfig;
echo $startup;
- echo "\n";
- // Note: The following has to be wrapped in a script tag because the startup module also
- // writes a script tag (the one loading mediawiki.js). Script tags are synchronous, block
- // each other, and run in order. But they don't nest. The code appended after the startup
- // module runs before the added script tag is parsed and executed.
- echo Xml::encodeJsCall( 'document.write', array( Html::inlineScript( $code ) ) );
+ // The following has to be deferred via RLQ because the startup module is asynchronous.
+ echo ResourceLoader::makeLoaderConditionalScript( $code );
}
private function plainQUnit() {
$out = $this->getOutput();
$out->disable();
- $url = $this->getPageTitle( 'qunit/export' )->getFullURL( array(
- 'debug' => ResourceLoader::inDebugMode() ? 'true' : 'false',
- ) );
-
$styles = $out->makeResourceLoaderLink( 'jquery.qunit',
ResourceLoaderModule::TYPE_STYLES
);
- // Use 'raw' since this is a plain HTML page without ResourceLoader
+
+ // Use 'raw' because QUnit loads before ResourceLoader initialises (omit mw.loader.state call)
+ // Use 'test' to ensure OutputPage doesn't use the "async" attribute because QUnit must
+ // load before qunit/export.
$scripts = $out->makeResourceLoaderLink( 'jquery.qunit',
ResourceLoaderModule::TYPE_SCRIPTS,
- array( 'raw' => 'true' )
+ array( 'raw' => true, 'sync' => true )
);
$head = implode( "\n", array_merge( $styles['html'], $scripts['html'] ) );
$summary
<div id="qunit"></div>
HTML;
+
+ $url = $this->getPageTitle( 'qunit/export' )->getFullURL( array(
+ 'debug' => ResourceLoader::inDebugMode() ? 'true' : 'false',
+ ) );
$html .= "\n" . Html::linkedScript( $url );
header( 'Content-Type: text/html; charset=utf-8' );
"special-characters-group-devanagari": "Devanagari",
"special-characters-group-thai": "Thai",
"special-characters-group-lao": "Lao",
- "special-characters-group-khmer": "Khmer"
+ "special-characters-group-khmer": "Khmer",
+ "mw-widgets-dateinput-placeholder-day": "JJJJ-MM-DD",
+ "mw-widgets-dateinput-placeholder-month": "JJJJ-MM"
}
"special-characters-group-gujarati": "Gujaratí",
"special-characters-group-thai": "Thai",
"special-characters-group-lao": "Laosiano",
- "special-characters-group-khmer": "Khmer"
+ "special-characters-group-khmer": "Khmer",
+ "mw-widgets-dateinput-placeholder-day": "AAAA-MM-DD",
+ "mw-widgets-dateinput-placeholder-month": "AAAA-MM"
}
"special-characters-title-endash": "guión curtiu",
"special-characters-title-emdash": "guión llargu",
"special-characters-title-minus": "signu menos",
+ "mw-widgets-dateinput-placeholder-day": "AAAA-MM-DD",
+ "mw-widgets-dateinput-placeholder-month": "AAAA-MM",
"mw-widgets-titleinput-description-new-page": "la páxina inda nun esiste",
"mw-widgets-titleinput-description-redirect": "redirixir a $1"
}
"expand_templates_remove_comments": "Tanggalon an mga komento",
"expand_templates_remove_nowiki": "Pugulan an mga taktak na <nowiki> sa resulta",
"expand_templates_generate_xml": "Ipahiling an panlunhay na kahoy nin XML",
- "expand_templates_preview": "Patânaw"
+ "expand_templates_preview": "Patânaw",
+ "mw-widgets-dateinput-placeholder-day": "TTTT-BB-AA",
+ "mw-widgets-dateinput-placeholder-month": "TTTT-BB"
}
"special-characters-title-endash": "кароткі працяжнік",
"special-characters-title-emdash": "доўгі працяжнік",
"special-characters-title-minus": "мінус",
+ "mw-widgets-dateinput-placeholder-day": "ГГГГ-ММ-ДД",
+ "mw-widgets-dateinput-placeholder-month": "ГГГГ-ММ",
"mw-widgets-titleinput-description-new-page": "старонка яшчэ не існуе"
}
"special-characters-group-khmer": "кхмерски",
"special-characters-title-endash": "средно тире",
"special-characters-title-emdash": "дълго тире",
- "special-characters-title-minus": "знак минус"
+ "special-characters-title-minus": "знак минус",
+ "mw-widgets-dateinput-placeholder-day": "ГГГГ-ММ-ДД",
+ "mw-widgets-dateinput-placeholder-month": "ГГГГ-ММ"
}
"special-characters-title-endash": "guió curt",
"special-characters-title-emdash": "guió llarg",
"special-characters-title-minus": "signe menys",
+ "mw-widgets-dateinput-placeholder-day": "AAAA-MM-DD",
+ "mw-widgets-dateinput-placeholder-month": "AAAA-MM",
"mw-widgets-titleinput-description-new-page": "la pàgina no existeix encara",
"mw-widgets-titleinput-description-redirect": "redirigeix a $1"
}
"special-characters-title-endash": "krátká pomlčka",
"special-characters-title-emdash": "dlouhá pomlčka",
"special-characters-title-minus": "znaménko minus",
+ "mw-widgets-dateinput-placeholder-day": "RRRR-MM-DD",
+ "mw-widgets-dateinput-placeholder-month": "RRRR-MM",
"mw-widgets-titleinput-description-new-page": "stránka zatím neexistuje",
"mw-widgets-titleinput-description-redirect": "přesměrování na $1"
}
"special-characters-group-khmer": "Chmereg",
"special-characters-title-endash": "heiffen en",
"special-characters-title-emdash": "heiffen em",
- "special-characters-title-minus": "arwydd minws"
+ "special-characters-title-minus": "arwydd minws",
+ "mw-widgets-dateinput-placeholder-day": "BBBB-MM-DD",
+ "mw-widgets-dateinput-placeholder-month": "BBBB-MM"
}
"special-characters-group-khmer": "Khmer",
"special-characters-title-endash": "tankestreg",
"special-characters-title-emdash": "lang tankestreg",
- "special-characters-title-minus": "minustegn"
+ "special-characters-title-minus": "minustegn",
+ "mw-widgets-dateinput-placeholder-day": "ÅÅÅÅ-MM-DD",
+ "mw-widgets-dateinput-placeholder-month": "ÅÅÅÅ-MM"
}
"special-characters-title-emdash": "Geviertstrich",
"special-characters-title-minus": "Minus",
"mw-widgets-dateinput-no-date": "Kein Datum ausgewählt",
+ "mw-widgets-dateinput-placeholder-day": "JJJJ-MM-TT",
+ "mw-widgets-dateinput-placeholder-month": "JJJJ-MM",
"mw-widgets-titleinput-description-new-page": "Seite ist noch nicht vorhanden",
"mw-widgets-titleinput-description-redirect": "Weiterleitung nach $1"
}
"special-characters-group-khmer": "Khmer",
"special-characters-title-endash": "tira kılme",
"special-characters-title-emdash": "tira derge",
- "special-characters-title-minus": "işaretê kemiye"
+ "special-characters-title-minus": "işaretê kemiye",
+ "mw-widgets-dateinput-placeholder-day": "SSSS-AA-RR",
+ "mw-widgets-dateinput-placeholder-month": "SSSS-AA"
}
"special-characters-group-khmer": "καμποτζιανά",
"special-characters-title-endash": "παύλα",
"special-characters-title-emdash": "διπλή παύλα",
- "special-characters-title-minus": "σύμβολο πλην"
+ "special-characters-title-minus": "σύμβολο πλην",
+ "mw-widgets-dateinput-placeholder-day": "ΕΕΕΕ-ΜΜ-ΗΗ",
+ "mw-widgets-dateinput-placeholder-month": "ΕΕΕΕ-ΜΜ"
}
"special-characters-title-emdash": "em dash",
"special-characters-title-minus": "minus sign",
"mw-widgets-dateinput-no-date": "No date selected",
+ "mw-widgets-dateinput-placeholder-day": "YYYY-MM-DD",
+ "mw-widgets-dateinput-placeholder-month": "YYYY-MM",
"mw-widgets-titleinput-description-new-page": "page does not exist yet",
"mw-widgets-titleinput-description-redirect": "redirect to $1"
}
"special-characters-group-khmer": "kmera",
"special-characters-title-endash": "mallonga streketo",
"special-characters-title-emdash": "longa streketo",
- "special-characters-title-minus": "minus-signo"
+ "special-characters-title-minus": "minus-signo",
+ "mw-widgets-dateinput-placeholder-day": "JJJJ-MM-TT",
+ "mw-widgets-dateinput-placeholder-month": "JJJJ-MM"
}
"special-characters-title-emdash": "raya",
"special-characters-title-minus": "signo de resta",
"mw-widgets-dateinput-no-date": "Ninguna fecha seleccionada",
+ "mw-widgets-dateinput-placeholder-day": "AAAA-MM-DD",
+ "mw-widgets-dateinput-placeholder-month": "AAAA-MM",
"mw-widgets-titleinput-description-new-page": "la página aún no existe",
"mw-widgets-titleinput-description-redirect": "redirigir a $1"
}
"special-characters-title-endash": "mõttekriips",
"special-characters-title-emdash": "pikk mõttekriips",
"special-characters-title-minus": "miinusmärk",
+ "mw-widgets-dateinput-placeholder-day": "AAAA-KK-PP",
+ "mw-widgets-dateinput-placeholder-month": "AAAA-KK",
"mw-widgets-titleinput-description-new-page": "lehekülge pole veel",
"mw-widgets-titleinput-description-redirect": "ümbersuunamine leheküljele \"$1\""
}
"special-characters-title-endash": "ajatusviiva",
"special-characters-title-emdash": "pitkä ajatusviiva",
"special-characters-title-minus": "miinusmerkki",
+ "mw-widgets-dateinput-placeholder-day": "VVVV-KK-PP",
+ "mw-widgets-dateinput-placeholder-month": "VVVV-KK",
"mw-widgets-titleinput-description-new-page": "sivua ei ole olemassa vielä",
"mw-widgets-titleinput-description-redirect": "ohjaus kohteeseen $1"
}
"duration-centuries": "$1 {{PLURAL:$1|øld|øldir}}",
"expand_templates_output": "Úrslit",
"expand_templates_ok": "Í lagi",
- "expand_templates_preview": "Forskoðan"
+ "expand_templates_preview": "Forskoðan",
+ "mw-widgets-dateinput-placeholder-day": "ÁÁÁÁ-MM-DD",
+ "mw-widgets-dateinput-placeholder-month": "ÁÁÁÁ-MM"
}
"special-characters-title-endash": "tiret demi-cadratin",
"special-characters-title-emdash": "tiret cadratin",
"special-characters-title-minus": "signe moins",
+ "mw-widgets-dateinput-placeholder-day": "AAAA-MM-JJ",
+ "mw-widgets-dateinput-placeholder-month": "AAAA-MM",
"mw-widgets-titleinput-description-new-page": "la page n’existe pas encore",
"mw-widgets-titleinput-description-redirect": "redirection vers $1"
}
"special-characters-group-khmer": "C’mère",
"special-characters-title-endash": "terèt anglès",
"special-characters-title-emdash": "terèt èm",
- "special-characters-title-minus": "segno muens"
+ "special-characters-title-minus": "segno muens",
+ "mw-widgets-dateinput-placeholder-day": "AAAA-MM-JJ",
+ "mw-widgets-dateinput-placeholder-month": "AAAA-MM"
}
"special-characters-title-endash": "guión",
"special-characters-title-emdash": "raia",
"special-characters-title-minus": "signo menos",
+ "mw-widgets-dateinput-placeholder-day": "AAAA-MM-DD",
+ "mw-widgets-dateinput-placeholder-month": "AAAA-MM",
"mw-widgets-titleinput-description-new-page": "a páxina aínda non existe",
"mw-widgets-titleinput-description-redirect": "redirección cara a $1"
}
"special-characters-group-devanagari": "Devanagari",
"special-characters-group-thai": "Thai",
"special-characters-group-lao": "Lao",
- "special-characters-group-khmer": "Khmer"
+ "special-characters-group-khmer": "Khmer",
+ "mw-widgets-dateinput-placeholder-day": "JJJJ-MM-TT",
+ "mw-widgets-dateinput-placeholder-month": "JJJJ-MM"
}
"special-characters-group-thai": "Tajlandski (tajski) znakovi",
"special-characters-group-lao": "laoski znakovi",
"special-characters-group-khmer": "kmerski",
+ "mw-widgets-dateinput-placeholder-day": "GGGG-MM-DD",
+ "mw-widgets-dateinput-placeholder-month": "GGGG-MM",
"mw-widgets-titleinput-description-redirect": "preusmjeravanje na $1"
}
"special-characters-group-khmer": "Khmeršćina",
"special-characters-title-endash": "krótka ležaca smužka",
"special-characters-title-emdash": "dołha ležaca smužka",
- "special-characters-title-minus": "minusowe znamješko"
+ "special-characters-title-minus": "minusowe znamješko",
+ "mw-widgets-dateinput-placeholder-day": "JJJJ-MM-TT",
+ "mw-widgets-dateinput-placeholder-month": "JJJJ-MM"
}
"special-characters-title-emdash": "hosszú kötőjel",
"special-characters-title-minus": "minusz jel",
"mw-widgets-dateinput-no-date": "Nincs kiválasztott dátum",
+ "mw-widgets-dateinput-placeholder-day": "ÉÉÉÉ-HH-NN",
+ "mw-widgets-dateinput-placeholder-month": "ÉÉÉÉ-HH",
"mw-widgets-titleinput-description-new-page": "a lap még nem létezik",
"mw-widgets-titleinput-description-redirect": "átirányítás ide: $1"
}
"special-characters-group-thai": "Thailandese",
"special-characters-group-lao": "Laotiano",
"special-characters-group-khmer": "Cambodgiano",
- "special-characters-title-minus": "signo minus"
+ "special-characters-title-minus": "signo minus",
+ "mw-widgets-dateinput-placeholder-day": "AAAA-MM-DD",
+ "mw-widgets-dateinput-placeholder-month": "AAAA-MM"
}
"special-characters-group-khmer": "Khmer",
"special-characters-title-endash": "en dash",
"special-characters-title-emdash": "em dash",
- "special-characters-title-minus": "tanda kurang"
+ "special-characters-title-minus": "tanda kurang",
+ "mw-widgets-dateinput-placeholder-day": "TTTT-BB-HH",
+ "mw-widgets-dateinput-placeholder-month": "TTTT-BB"
}
"special-characters-title-endash": "en dash",
"special-characters-title-emdash": "em dash",
"special-characters-title-minus": "senial a panagkissay",
+ "mw-widgets-dateinput-placeholder-day": "TTTT-BB-AA",
+ "mw-widgets-dateinput-placeholder-month": "TTTT-BB",
"mw-widgets-titleinput-description-new-page": "awan pay ti panid",
"mw-widgets-titleinput-description-redirect": "ibaw-ing iti $1"
}
"special-characters-title-endash": "lineetta enne",
"special-characters-title-emdash": "lineetta emme",
"special-characters-title-minus": "segno meno",
+ "mw-widgets-dateinput-placeholder-day": "AAAA-MM-GG",
+ "mw-widgets-dateinput-placeholder-month": "AAAA-MM",
"mw-widgets-titleinput-description-new-page": "questa pagina non esiste ancora",
"mw-widgets-titleinput-description-redirect": "reindirizzamento a $1"
}
"special-characters-title-endash": "ტირე",
"special-characters-title-emdash": "გრძელი ტირე",
"special-characters-title-minus": "მინუსის ნიშანი",
+ "mw-widgets-dateinput-placeholder-day": "წწწწ-თთ-დდ",
+ "mw-widgets-dateinput-placeholder-month": "წწწწ-თთ",
"mw-widgets-titleinput-description-new-page": "გვერდი ჯერ არ არსებობს",
"mw-widgets-titleinput-description-redirect": "გადამისამართება $1-ზე"
}
"special-characters-title-endash": "дефис",
"special-characters-title-emdash": "сызықша",
"special-characters-title-minus": "минус белгісі",
+ "mw-widgets-dateinput-placeholder-day": "ЖЖЖЖ-АА-КК",
+ "mw-widgets-dateinput-placeholder-month": "ЖЖЖЖ-АА",
"mw-widgets-titleinput-description-new-page": "бет жоқ екен",
"mw-widgets-titleinput-description-redirect": "$1 дегенге бағыттату"
}
"special-characters-group-khmer": "Khmer",
"special-characters-title-minus": "Minus-Zeechen",
"mw-widgets-dateinput-no-date": "Keen Datum erausgesicht",
+ "mw-widgets-dateinput-placeholder-day": "JJJJ-MM-DD",
+ "mw-widgets-dateinput-placeholder-month": "JJJJ-MM",
"mw-widgets-titleinput-description-new-page": "Säit gëtt et nach net",
"mw-widgets-titleinput-description-redirect": "viruleeden op $1"
}
"special-characters-group-devanagari": "Devanagari",
"special-characters-group-thai": "Thai",
"special-characters-group-lao": "Lao",
- "special-characters-group-khmer": "Cambodzjaans"
+ "special-characters-group-khmer": "Cambodzjaans",
+ "mw-widgets-dateinput-placeholder-day": "JJJJ-MM-DD",
+ "mw-widgets-dateinput-placeholder-month": "JJJJ-MM"
}
"feedback-message": "Thu",
"feedback-cancel": "Sûtna",
"searchsuggest-search": "Zawnna",
- "searchsuggest-containing": "hemi thu telna hi..."
+ "searchsuggest-containing": "hemi thu telna hi...",
+ "mw-widgets-dateinput-placeholder-day": "KKKK-TT-NN",
+ "mw-widgets-dateinput-placeholder-month": "KKKK-TT"
}
"special-characters-title-emdash": "тире",
"special-characters-title-minus": "минус",
"mw-widgets-dateinput-no-date": "Немате одбрано датум",
+ "mw-widgets-dateinput-placeholder-day": "ГГГГ-ММ-ДД",
+ "mw-widgets-dateinput-placeholder-month": "ГГГГ-ММ",
"mw-widgets-titleinput-description-new-page": "страницата сè уште не постои",
"mw-widgets-titleinput-description-redirect": "пренасочување кон $1"
}
"special-characters-group-devanagari": "Devanagari",
"special-characters-group-thai": "Tajlandiż",
"special-characters-group-lao": "Lao",
- "special-characters-group-khmer": "Khmer"
+ "special-characters-group-khmer": "Khmer",
+ "mw-widgets-dateinput-placeholder-day": "SSSS-XX-ĠĠ",
+ "mw-widgets-dateinput-placeholder-month": "SSSS-XX"
}
"special-characters-group-khmer": "Khmer",
"special-characters-title-endash": "tankestrek",
"special-characters-title-emdash": "lang tankestrek",
- "special-characters-title-minus": "minustegn"
+ "special-characters-title-minus": "minustegn",
+ "mw-widgets-dateinput-placeholder-day": "ÅÅÅÅ-MM-DD",
+ "mw-widgets-dateinput-placeholder-month": "ÅÅÅÅ-MM"
}
"special-characters-title-endash": "liggend streepje",
"special-characters-title-emdash": "gedachtenstreepje",
"special-characters-title-minus": "minteken",
+ "mw-widgets-dateinput-placeholder-day": "JJJJ-MM-DD",
+ "mw-widgets-dateinput-placeholder-month": "JJJJ-MM",
"mw-widgets-titleinput-description-new-page": "pagina bestaat nog niet",
"mw-widgets-titleinput-description-redirect": "doorverwijzing naar $1"
}
"special-characters-group-bangla": "Bengali",
"special-characters-group-telugu": "Telugu",
"special-characters-group-sinhala": "Singalesisk",
- "special-characters-group-gujarati": "Gujarati"
+ "special-characters-group-gujarati": "Gujarati",
+ "mw-widgets-dateinput-placeholder-day": "ÅÅÅÅ-MM-DD",
+ "mw-widgets-dateinput-placeholder-month": "ÅÅÅÅ-MM"
}
"special-characters-title-emdash": "pauza",
"special-characters-title-minus": "minus",
"mw-widgets-dateinput-no-date": "Nie wybrano daty",
+ "mw-widgets-dateinput-placeholder-day": "RRRR-MM-DD",
+ "mw-widgets-dateinput-placeholder-month": "RRRR-MM",
"mw-widgets-titleinput-description-new-page": "strona jeszcze nie istnieje",
"mw-widgets-titleinput-description-redirect": "przekierowanie do $1"
}
"special-characters-group-khmer": "Khmer",
"special-characters-title-endash": "tratin en",
"special-characters-title-emdash": "tratin em",
- "special-characters-title-minus": "segn meno"
+ "special-characters-title-minus": "segn meno",
+ "mw-widgets-dateinput-placeholder-day": "AAAA-MM-DD",
+ "mw-widgets-dateinput-placeholder-month": "AAAA-MM"
}
"special-characters-group-khmer": "Cambojano",
"special-characters-title-endash": "traço",
"special-characters-title-emdash": "travessão",
- "special-characters-title-minus": "sinal de menos"
+ "special-characters-title-minus": "sinal de menos",
+ "mw-widgets-dateinput-placeholder-day": "AAAA-MM-DD",
+ "mw-widgets-dateinput-placeholder-month": "AAAA-MM"
}
"special-characters-title-endash": "hífen",
"special-characters-title-emdash": "travessão",
"special-characters-title-minus": "sinal de subtração",
+ "mw-widgets-dateinput-placeholder-day": "AAAA-MM-DD",
+ "mw-widgets-dateinput-placeholder-month": "AAAA-MM",
"mw-widgets-titleinput-description-new-page": "a página ainda não existe.",
"mw-widgets-titleinput-description-redirect": "redirecionar para $1"
}
"special-characters-title-emdash": "Title tooltip for the em dash character (—); See https://en.wikipedia.org/wiki/Dash",
"special-characters-title-minus": "Title tooltip for the minus sign character (−), not to be confused with a hyphen",
"mw-widgets-dateinput-no-date": "Label of a date input field when no date has been selected.",
+ "mw-widgets-dateinput-placeholder-day": "[[File:DateInputWidget active, empty.png|frame|Screenshot]]\nPlaceholder displayed in a date input field when it's empty, representing a date format with 4 digits for year, 2 digits for month, and 2 digits for day, separated with hyphens. This should be uppercase, if possible, and must not include any additional explanations. If there is no good way to translate it, make this message blank.",
+ "mw-widgets-dateinput-placeholder-month": "Placeholder displayed in a date input field when it's empty, representing a date format with 4 digits for year and 2 digits for month, separated with hyphens (without a day). This should be uppercase, if possible, and must not include any additional explanations. If there is no good way to translate it, make this message blank.",
"mw-widgets-titleinput-description-new-page": "Description label for a new page in the title input widget.",
"mw-widgets-titleinput-description-redirect": "Description label for a redirect in the title input widget."
}
"special-characters-title-emdash": "linie de pauză (em dash)",
"special-characters-title-minus": "semnul minus",
"mw-widgets-dateinput-no-date": "Nicio dată selectată",
+ "mw-widgets-dateinput-placeholder-day": "AAAA-LL-ZZ",
+ "mw-widgets-dateinput-placeholder-month": "AAAA-LL",
"mw-widgets-titleinput-description-new-page": "pagina nu există încă",
"mw-widgets-titleinput-description-redirect": "redirecționare către $1"
}
"special-characters-title-endash": "среднее тире",
"special-characters-title-emdash": "длинное тире",
"special-characters-title-minus": "знак минус",
+ "mw-widgets-dateinput-placeholder-day": "ГГГГ-ММ-ДД",
+ "mw-widgets-dateinput-placeholder-month": "ГГГГ-ММ",
"mw-widgets-titleinput-description-new-page": "страница ещё не существует",
"mw-widgets-titleinput-description-redirect": "перенаправление на $1"
}
"special-characters-group-gujarati": "Ґуджараті",
"special-characters-group-thai": "Тайськы",
"special-characters-group-lao": "Лаоськы",
- "special-characters-group-khmer": "Кгмерськы"
+ "special-characters-group-khmer": "Кгмерськы",
+ "mw-widgets-dateinput-placeholder-day": "РРРР-ММ-ДД",
+ "mw-widgets-dateinput-placeholder-month": "РРРР-ММ"
}
"special-characters-group-khmer": "Khmer",
"special-characters-title-endash": "pomlčka",
"special-characters-title-emdash": "dlhá pomlčka",
- "special-characters-title-minus": "mínus"
+ "special-characters-title-minus": "mínus",
+ "mw-widgets-dateinput-placeholder-day": "RRRR-MM-DD",
+ "mw-widgets-dateinput-placeholder-month": "RRRR-MM"
}
"special-characters-title-emdash": "dolgi pomišljaj",
"special-characters-title-minus": "znak za minus",
"mw-widgets-dateinput-no-date": "Datum ni izbran",
+ "mw-widgets-dateinput-placeholder-day": "LLLL-MM-DD",
+ "mw-widgets-dateinput-placeholder-month": "LLLL-MM",
"mw-widgets-titleinput-description-new-page": "stran še ne obstaja",
"mw-widgets-titleinput-description-redirect": "preusmeritev na $1"
}
"special-characters-group-gujarati": "Guxharati",
"special-characters-group-thai": "Thai",
"special-characters-group-lao": "Lao",
- "special-characters-group-khmer": "Khmer"
+ "special-characters-group-khmer": "Khmer",
+ "mw-widgets-dateinput-placeholder-day": "VVVV-MM-DD",
+ "mw-widgets-dateinput-placeholder-month": "VVVV-MM"
}
"special-characters-group-thai": "тајландски",
"special-characters-group-lao": "лаоски",
"special-characters-group-khmer": "кмерски",
+ "mw-widgets-dateinput-placeholder-day": "ГГГГ-ММ-ДД",
+ "mw-widgets-dateinput-placeholder-month": "ГГГГ-ММ",
"mw-widgets-titleinput-description-new-page": "страница још увек не постоји",
"mw-widgets-titleinput-description-redirect": "преусмерава на $1"
}
"special-characters-group-thai": "tajlandski",
"special-characters-group-lao": "laoski",
"special-characters-group-khmer": "kmerski",
+ "mw-widgets-dateinput-placeholder-day": "GGGG-MM-DD",
+ "mw-widgets-dateinput-placeholder-month": "GGGG-MM",
"mw-widgets-titleinput-description-new-page": "stranica još uvek ne postoji",
"mw-widgets-titleinput-description-redirect": "preusmerava na $1"
}
"special-characters-title-emdash": "långt tankstreck",
"special-characters-title-minus": "minustecken",
"mw-widgets-dateinput-no-date": "Inget valt datum",
+ "mw-widgets-dateinput-placeholder-day": "ÅÅÅÅ-MM-DD",
+ "mw-widgets-dateinput-placeholder-month": "ÅÅÅÅ-MM",
"mw-widgets-titleinput-description-new-page": "sidan existerar inte ännu",
"mw-widgets-titleinput-description-redirect": "omdirigerar till $1"
}
"special-characters-group-devanagari": "Devanagari",
"special-characters-group-thai": "Thai",
"special-characters-group-lao": "Lao",
- "special-characters-group-khmer": "Khmer"
+ "special-characters-group-khmer": "Khmer",
+ "mw-widgets-dateinput-placeholder-day": "TTTT-BB-AA",
+ "mw-widgets-dateinput-placeholder-month": "TTTT-BB"
}
"special-characters-title-emdash": "довге тире",
"special-characters-title-minus": "мінус",
"mw-widgets-dateinput-no-date": "Дати не вибрано",
+ "mw-widgets-dateinput-placeholder-day": "РРРР-ММ-ДД",
+ "mw-widgets-dateinput-placeholder-month": "РРРР-ММ",
"mw-widgets-titleinput-description-new-page": "сторінка ще не існує",
"mw-widgets-titleinput-description-redirect": "перенаправлення на $1"
}
"special-characters-group-khmer": "Khmer",
"special-characters-title-endash": "lineeta ene",
"special-characters-title-emdash": "lineeta eme",
- "special-characters-title-minus": "segno meno"
+ "special-characters-title-minus": "segno meno",
+ "mw-widgets-dateinput-placeholder-day": "AAAA-MM-GG",
+ "mw-widgets-dateinput-placeholder-month": "AAAA-MM"
}
/* MediaWiki Legacy */
- 'mediawiki.legacy.ajax' => array(
- 'scripts' => 'resources/src/mediawiki.legacy/ajax.js',
- 'dependencies' => array(
- 'mediawiki.util',
- 'mediawiki.legacy.wikibits',
- ),
- 'position' => 'top',
- ),
'mediawiki.legacy.commonPrint' => array(
'position' => 'top',
'styles' => array(
),
'messages' => array(
'mw-widgets-dateinput-no-date',
+ 'mw-widgets-dateinput-placeholder-day',
+ 'mw-widgets-dateinput-placeholder-month',
'mw-widgets-titleinput-description-new-page',
'mw-widgets-titleinput-description-redirect',
),
+++ /dev/null
-/**
- * Remote Scripting Library
- * Copyright 2005 modernmethod, inc
- * Under the open source BSD license
- * http://www.modernmethod.com/sajax/
- */
-
-/*jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
-/*global alert */
-( function ( mw ) {
-
- /**
- * if sajax_debug_mode is true, this function outputs given the message into
- * the element with id = sajax_debug; if no such element exists in the document,
- * it is injected.
- */
- function debug( text ) {
- if ( !window.sajax_debug_mode ) {
- return false;
- }
-
- var b, m,
- e = document.getElementById( 'sajax_debug' );
-
- if ( !e ) {
- e = document.createElement( 'p' );
- e.className = 'sajax_debug';
- e.id = 'sajax_debug';
-
- b = document.getElementsByTagName( 'body' )[0];
-
- if ( b.firstChild ) {
- b.insertBefore( e, b.firstChild );
- } else {
- b.appendChild( e );
- }
- }
-
- m = document.createElement( 'div' );
- m.appendChild( document.createTextNode( text ) );
-
- e.appendChild( m );
-
- return true;
- }
-
- /**
- * Compatibility wrapper for creating a new XMLHttpRequest object.
- */
- function createXhr() {
- debug( 'sajax_init_object() called..' );
- var a;
- try {
- // Try the new style before ActiveX so we don't
- // unnecessarily trigger warnings in IE 7 when
- // set to prompt about ActiveX usage
- a = new XMLHttpRequest();
- } catch ( xhrE ) {
- try {
- a = new window.ActiveXObject( 'Msxml2.XMLHTTP' );
- } catch ( msXmlE ) {
- try {
- a = new window.ActiveXObject( 'Microsoft.XMLHTTP' );
- } catch ( msXhrE ) {
- a = null;
- }
- }
- }
- if ( !a ) {
- debug( 'Could not create connection object.' );
- }
-
- return a;
- }
-
- /**
- * Perform an AJAX call to MediaWiki. Calls are handled by AjaxDispatcher.php
- * func_name - the name of the function to call. Must be registered in $wgAjaxExportList
- * args - an array of arguments to that function
- * target - the target that will handle the result of the call. If this is a function,
- * if will be called with the XMLHttpRequest as a parameter; if it's an input
- * element, its value will be set to the resultText; if it's another type of
- * element, its innerHTML will be set to the resultText.
- *
- * Example:
- * sajax_do_call( 'doFoo', [1, 2, 3], document.getElementById( 'showFoo' ) );
- *
- * This will call the doFoo function via MediaWiki's AjaxDispatcher, with
- * (1, 2, 3) as the parameter list, and will show the result in the element
- * with id = showFoo
- */
- function doAjaxRequest( func_name, args, target ) {
- var i, x, uri, post_data;
- uri = mw.util.wikiScript() + '?action=ajax';
- if ( window.sajax_request_type === 'GET' ) {
- if ( uri.indexOf( '?' ) === -1 ) {
- uri = uri + '?rs=' + encodeURIComponent( func_name );
- } else {
- uri = uri + '&rs=' + encodeURIComponent( func_name );
- }
- for ( i = 0; i < args.length; i++ ) {
- uri = uri + '&rsargs[]=' + encodeURIComponent( args[i] );
- }
- // uri = uri + '&rsrnd=' + new Date().getTime();
- post_data = null;
- } else {
- post_data = 'rs=' + encodeURIComponent( func_name );
- for ( i = 0; i < args.length; i++ ) {
- post_data = post_data + '&rsargs[]=' + encodeURIComponent( args[i] );
- }
- }
- x = createXhr();
- if ( !x ) {
- alert( 'AJAX not supported' );
- return false;
- }
-
- try {
- x.open( window.sajax_request_type, uri, true );
- } catch ( e ) {
- if ( location.hostname === 'localhost' ) {
- alert( 'Your browser blocks XMLHttpRequest to "localhost", try using a real hostname for development/testing.' );
- }
- throw e;
- }
- if ( window.sajax_request_type === 'POST' ) {
- x.setRequestHeader( 'Method', 'POST ' + uri + ' HTTP/1.1' );
- x.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' );
- }
- x.setRequestHeader( 'Pragma', 'cache=yes' );
- x.setRequestHeader( 'Cache-Control', 'no-transform' );
- x.onreadystatechange = function () {
- if ( x.readyState !== 4 ) {
- return;
- }
-
- debug( 'received (' + x.status + ' ' + x.statusText + ') ' + x.responseText );
-
- // if ( x.status != 200 )
- // alert( 'Error: ' + x.status + ' ' + x.statusText + ': ' + x.responseText );
- // else
-
- if ( typeof target === 'function' ) {
- target( x );
- } else if ( typeof target === 'object' ) {
- if ( target.tagName === 'INPUT' ) {
- if ( x.status === 200 ) {
- target.value = x.responseText;
- }
- // else alert( 'Error: ' + x.status + ' ' + x.statusText + ' (' + x.responseText + ')' );
- } else {
- if ( x.status === 200 ) {
- target.innerHTML = x.responseText;
- } else {
- target.innerHTML = '<div class="error">Error: ' + x.status +
- ' ' + x.statusText + ' (' + x.responseText + ')</div>';
- }
- }
- } else {
- alert( 'Bad target for sajax_do_call: not a function or object: ' + target );
- }
- };
-
- debug( func_name + ' uri = ' + uri + ' / post = ' + post_data );
- x.send( post_data );
- debug( func_name + ' waiting..' );
-
- return true;
- }
-
- /**
- * @return {boolean} Whether the browser supports AJAX
- */
- function wfSupportsAjax() {
- var request = createXhr(),
- supportsAjax = request ? true : false;
-
- request = undefined;
- return supportsAjax;
- }
-
- // Expose + Mark as deprecated
- var deprecationNotice = 'Sajax is deprecated, use jQuery.ajax or mediawiki.api instead.';
-
- // Variables
- mw.log.deprecate( window, 'sajax_debug_mode', false, deprecationNotice );
- mw.log.deprecate( window, 'sajax_request_type', 'GET', deprecationNotice );
- // Methods
- mw.log.deprecate( window, 'sajax_debug', debug, deprecationNotice );
- mw.log.deprecate( window, 'sajax_init_object', createXhr, deprecationNotice );
- mw.log.deprecate( window, 'sajax_do_call', doAjaxRequest, deprecationNotice );
- mw.log.deprecate( window, 'wfSupportsAjax', wfSupportsAjax, deprecationNotice );
-
-}( mediaWiki ) );
var $spinner, href, rcid, apiRequest;
// Start preloading the notification module (normally loaded by mw.notify())
- mw.loader.load( ['mediawiki.notification'], null, true );
+ mw.loader.load( 'mediawiki.notification' );
// Hide the link and create a spinner to show it inside the brackets.
$spinner = $.createSpinner( {
mw.notify( mw.msg( 'markedaspatrollednotify', title.toText() ) );
} else {
// This should never happen as errors should trigger fail
- mw.notify( mw.msg( 'markedaspatrollederrornotify' ) );
+ mw.notify( mw.msg( 'markedaspatrollederrornotify' ), { type: 'error' } );
}
} )
.fail( function ( error ) {
$patrolLinks.show();
if ( error === 'noautopatrol' ) {
// Can't patrol own
- mw.notify( mw.msg( 'markedaspatrollederror-noautopatrol' ) );
+ mw.notify( mw.msg( 'markedaspatrollederror-noautopatrol' ), { type: 'warn' } );
} else {
- mw.notify( mw.msg( 'markedaspatrollederrornotify' ) );
+ mw.notify( mw.msg( 'markedaspatrollederrornotify' ), { type: 'error' } );
}
} );
( function ( mw, $ ) {
- mw.page = {};
+ // Support: MediaWiki < 1.26
+ // Cached HTML will not yet have this from OutputPage::getHeadScripts.
+ document.documentElement.className = document.documentElement.className
+ .replace( /(^|\s)client-nojs(\s|$)/, '$1client-js$2' );
- // Client profile classes for <html>
- // Allows for easy hiding/showing of JS or no-JS-specific UI elements
- $( document.documentElement )
- .addClass( 'client-js' )
- .removeClass( 'client-nojs' );
+ mw.page = {};
$( function () {
mw.util.init();
var action, api, $link;
// Start preloading the notification module (normally loaded by mw.notify())
- mw.loader.load( ['mediawiki.notification'], null, true );
+ mw.loader.load( 'mediawiki.notification' );
action = mwUriGetAction( this.href );
msg = mw.message( 'watcherrortext', link );
// Report to user about the error
- mw.notify( msg, { tag: 'watch-self' } );
+ mw.notify( msg, {
+ tag: 'watch-self',
+ type: 'error'
+ } );
} );
} );
} );
mw.notify( mw.msg( 'addedwatchtext-short', title ) );
} ).fail( function () {
$link.text( mw.msg( 'watch' ) );
- mw.notify( mw.msg( 'watcherrortext', title ) );
+ mw.notify( mw.msg( 'watcherrortext', title ), { type: 'error' } );
} );
} else {
$link.text( mw.msg( 'unwatching' ) );
mw.notify( mw.msg( 'removedwatchtext-short', title ) );
} ).fail( function () {
$link.text( mw.msg( 'unwatch' ) );
- mw.notify( mw.msg( 'watcherrortext', title ) );
+ mw.notify( mw.msg( 'watcherrortext', title ), { type: 'error' } );
} );
}
/**
* Creates an mw.widgets.CalendarWidget object.
*
+ * You will most likely want to use mw.widgets.DateInputWidget instead of CalendarWidget directly.
+ *
* @class
* @extends OO.ui.Widget
* @mixins OO.ui.mixin.TabIndexedElement
* @constructor
* @param {Object} [config] Configuration options
* @cfg {string} [precision='day'] Date precision to use, 'day' or 'month'
- * @cfg {string|null} [date=null] Day or month date (depending on `precision`), in the
- * format 'YYYY-MM-DD' or 'YYYY-MM'. When null, defaults to current date.
+ * @cfg {string|null} [date=null] Day or month date (depending on `precision`), in the format
+ * 'YYYY-MM-DD' or 'YYYY-MM'. When null, the calendar will show today's date, but not select
+ * it.
*/
mw.widgets.CalendarWidget = function MWWCalendarWidget( config ) {
// Config initialization
if (
this.displayLayer === this.previousDisplayLayer &&
+ this.date === this.previousDate &&
this.previousMoment &&
this.previousMoment.isSame( this.moment, this.precision === 'month' ? 'month' : 'day' )
) {
this.previousMoment = moment( this.moment );
this.previousDisplayLayer = this.displayLayer;
+ this.previousDate = this.date;
this.$body.on( 'click', this.onBodyClick.bind( this ) );
};
* Set the date.
*
* @param {string|null} [date=null] Day or month date, in the format 'YYYY-MM-DD' or 'YYYY-MM'.
- * When null, defaults to current date. When invalid, the date is not changed.
+ * When null, the calendar will show today's date, but not select it. When invalid, the date
+ * is not changed.
*/
mw.widgets.CalendarWidget.prototype.setDate = function ( date ) {
var mom = date !== null ? moment( date, this.getDateFormat() ) : moment();
if ( mom.isValid() ) {
this.moment = mom;
- this.setDateFromMoment();
+ if ( date !== null ) {
+ this.setDateFromMoment();
+ } else if ( this.date !== null ) {
+ this.date = null;
+ this.emit( 'change', this.date );
+ }
this.displayLayer = this.getDisplayLayers()[ 0 ];
this.updateUI();
}
* Get current date, in the format 'YYYY-MM-DD' or 'YYYY-MM', depending on precision. Digits will
* not be localised.
*
- * @returns {string} Date string
+ * @returns {string|null} Date string
*/
mw.widgets.CalendarWidget.prototype.getDate = function () {
return this.date;
}
.mw-widget-calendarWidget-day-today {
- border: 1px solid #3787fb;
+ box-shadow: inset 0 0 0 1px #3787fb;
border-radius: ((@calendarHeight / 7) / 2);
- margin: -1px;
}
.mw-widget-calendarWidget-item-selected {
&.mw-widget-calendarWidget-day-heading {
border-radius: ((@calendarHeight / 7) / 4);
// Hide the border from .mw-widget-calendarWidget-day-today
- border: 0;
- margin: 0;
+ box-shadow: none;
}
&.mw-widget-calendarWidget-month {
/**
* Creates an mw.widgets.DateInputWidget object.
*
+ * @example
+ * // Date input widget showcase
+ * var fieldset = new OO.ui.FieldsetLayout( {
+ * items: [
+ * new OO.ui.FieldLayout(
+ * new mw.widgets.DateInputWidget(),
+ * {
+ * align: 'top',
+ * label: 'Select date'
+ * }
+ * ),
+ * new OO.ui.FieldLayout(
+ * new mw.widgets.DateInputWidget( { precision: 'month' } ),
+ * {
+ * align: 'top',
+ * label: 'Select month'
+ * }
+ * ),
+ * new OO.ui.FieldLayout(
+ * new mw.widgets.DateInputWidget( {
+ * inputFormat: 'DD.MM.YYYY',
+ * displayFormat: 'Do [of] MMMM [anno Domini] YYYY'
+ * } ),
+ * {
+ * align: 'top',
+ * label: 'Select date (custom formats)'
+ * }
+ * )
+ * ]
+ * } );
+ * $( 'body' ).append( fieldset.$element );
+ *
+ * The value is stored in 'YYYY-MM-DD' or 'YYYY-MM' format:
+ *
+ * @example
+ * // Accessing values in a date input widget
+ * var dateInput = new mw.widgets.DateInputWidget();
+ * var $label = $( '<p>' );
+ * $( 'body' ).append( $label, dateInput.$element );
+ * dateInput.on( 'change', function () {
+ * // The value will always be a valid date or empty string, malformed input is ignored
+ * var date = dateInput.getValue();
+ * $label.text( 'Selected date: ' + ( date || '(none)' ) );
+ * } );
+ *
* @class
* @extends OO.ui.InputWidget
*
* while the widget is inactive. Should be as unambiguous as possible (for example, prefer to
* spell out the month, rather than rely on the order), even if that makes it longer. When not
* given, the default is language-specific.
+ * @cfg {string} [placeholder] User-visible date format string displayed in the textual input
+ * field when it's empty. Should be the same as `inputFormat`, but translated to the user's
+ * language. When not given, defaults to a translated version of 'YYYY-MM-DD' or 'YYYY-MM',
+ * depending on `precision`.
*/
mw.widgets.DateInputWidget = function MWWDateInputWidget( config ) {
// Config initialization
- config = config || {};
+ config = $.extend( { precision: 'day' }, config );
+
+ var placeholder;
+ if ( config.placeholder ) {
+ placeholder = config.placeholder;
+ } else if ( config.inputFormat ) {
+ // We have no way to display a translated placeholder for custom formats
+ placeholder = '';
+ } else {
+ // Messages: mw-widgets-dateinput-placeholder-day, mw-widgets-dateinput-placeholder-month
+ placeholder = mw.msg( 'mw-widgets-dateinput-placeholder-' + config.precision );
+ }
// Properties (must be set before parent constructor, which calls #setValue)
this.handle = new OO.ui.LabelWidget();
this.textInput = new OO.ui.TextInputWidget( {
+ placeholder: placeholder,
validate: this.validateDate.bind( this )
} );
this.calendar = new mw.widgets.CalendarWidget( {
* @private
*/
mw.widgets.DateInputWidget.prototype.activate = function () {
- if ( this.getValue() === '' ) {
- // Setting today's date is probably more helpful than leaving the widget empty? We could just
- // display the placeholder and leave it there, but it's likely that at least the year will be
- // the same as today's.
-
- // Use English locale to avoid number formatting
- this.setValue( moment().locale( 'en' ).format( this.getInternalFormat() ) );
- }
-
this.$element.addClass( 'mw-widget-dateInputWidget-active' );
this.handle.toggle( false );
this.textInput.toggle( true );
border: 1px solid #ccc;
border-radius: 0.1em;
line-height: 1.275em;
+ background-color: white;
}
> .oo-ui-textInputWidget input {
}
/**
- * Adds a script tag to the DOM, either using document.write or low-level DOM manipulation,
- * depending on whether document-ready has occurred yet and whether we are in async mode.
+ * Load and execute a script with callback.
*
* @private
* @param {string} src URL to script, will be used as the src attribute in the script tag
* @param {Function} [callback] Callback which will be run when the script is done
- * @param {boolean} [async=false] Whether to load modules asynchronously.
- * Ignored (and defaulted to `true`) if the document-ready event has already occurred.
*/
- function addScript( src, callback, async ) {
- // Using isReady directly instead of storing it locally from a $().ready callback (bug 31895)
- if ( $.isReady || async ) {
- $.ajax( {
- url: src,
- dataType: 'script',
- // Force jQuery behaviour to be for crossDomain. Otherwise jQuery would use
- // XHR for a same domain request instead of <script>, which changes the request
- // headers (potentially missing a cache hit), and reduces caching in general
- // since browsers cache XHR much less (if at all). And XHR means we retreive
- // text, so we'd need to $.globalEval, which then messes up line numbers.
- crossDomain: true,
- cache: true,
- async: true
- } ).always( callback );
- } else {
- /*jshint evil:true */
- document.write( mw.html.element( 'script', { 'src': src }, '' ) );
- if ( callback ) {
- // Document.write is synchronous, so this is called when it's done.
- // FIXME: That's a lie. doc.write isn't actually synchronous.
- callback();
- }
- }
+ function addScript( src, callback ) {
+ $.ajax( {
+ url: src,
+ dataType: 'script',
+ // Force jQuery behaviour to be for crossDomain. Otherwise jQuery would use
+ // XHR for a same domain request instead of <script>, which changes the request
+ // headers (potentially missing a cache hit), and reduces caching in general
+ // since browsers cache XHR much less (if at all). And XHR means we retreive
+ // text, so we'd need to $.globalEval, which then messes up line numbers.
+ crossDomain: true,
+ cache: true
+ } ).always( callback );
}
/**
registry[module].state = 'ready';
handlePending( module );
};
- nestedAddScript = function ( arr, callback, async, i ) {
+ nestedAddScript = function ( arr, callback, i ) {
// Recursively call addScript() in its own callback
// for each element of arr.
if ( i >= arr.length ) {
}
addScript( arr[i], function () {
- nestedAddScript( arr, callback, async, i + 1 );
- }, async );
+ nestedAddScript( arr, callback, i + 1 );
+ } );
};
if ( $.isArray( script ) ) {
- nestedAddScript( script, markModuleReady, registry[module].async, 0 );
+ nestedAddScript( script, markModuleReady, 0 );
} else if ( $.isFunction( script ) ) {
// Pass jQuery twice so that the signature of the closure which wraps
// the script can bind both '$' and 'jQuery'.
mw.templates.set( module, registry[module].templates );
}
- if ( $.isReady || registry[module].async ) {
- // Make sure we don't run the scripts until all (potentially asynchronous)
- // stylesheet insertions have completed.
- ( function () {
- var pending = 0;
- checkCssHandles = function () {
- // cssHandlesRegistered ensures we don't take off too soon, e.g. when
- // one of the cssHandles is fired while we're still creating more handles.
- if ( cssHandlesRegistered && pending === 0 && runScript ) {
- runScript();
- runScript = undefined; // Revoke
+ // Make sure we don't run the scripts until all stylesheet insertions have completed.
+ ( function () {
+ var pending = 0;
+ checkCssHandles = function () {
+ // cssHandlesRegistered ensures we don't take off too soon, e.g. when
+ // one of the cssHandles is fired while we're still creating more handles.
+ if ( cssHandlesRegistered && pending === 0 && runScript ) {
+ runScript();
+ runScript = undefined; // Revoke
+ }
+ };
+ cssHandle = function () {
+ var check = checkCssHandles;
+ pending++;
+ return function () {
+ if ( check ) {
+ pending--;
+ check();
+ check = undefined; // Revoke
}
};
- cssHandle = function () {
- var check = checkCssHandles;
- pending++;
- return function () {
- if ( check ) {
- pending--;
- check();
- check = undefined; // Revoke
- }
- };
- };
- }() );
- } else {
- // We are in blocking mode, and so we can't afford to wait for CSS
- cssHandle = function () {};
- // Run immediately
- checkCssHandles = runScript;
- }
+ };
+ }() );
// Process styles (see also mw.loader.implement)
// * back-compat: { <media>: css }
* @param {string|string[]} dependencies Module name or array of string module names
* @param {Function} [ready] Callback to execute when all dependencies are ready
* @param {Function} [error] Callback to execute when any dependency fails
- * @param {boolean} [async=false] Whether to load modules asynchronously.
- * Ignored (and defaulted to `true`) if the document-ready event has already occurred.
*/
- function request( dependencies, ready, error, async ) {
+ function request( dependencies, ready, error ) {
// Allow calling by single module name
if ( typeof dependencies === 'string' ) {
dependencies = [dependencies];
return;
}
queue.push( module );
- if ( async ) {
- registry[module].async = true;
- }
}
} );
}
/**
- * Asynchronously append a script tag to the end of the body
- * that invokes load.php
+ * Load modules from load.php
* @private
* @param {Object} moduleMap Module map, see #buildModulesString
* @param {Object} currReqBase Object with other parameters (other than 'modules') to use in the request
* @param {string} sourceLoadScript URL of load.php
- * @param {boolean} async Whether to load modules asynchronously.
- * Ignored (and defaulted to `true`) if the document-ready event has already occurred.
*/
- function doRequest( moduleMap, currReqBase, sourceLoadScript, async ) {
+ function doRequest( moduleMap, currReqBase, sourceLoadScript ) {
var request = $.extend(
{ modules: buildModulesString( moduleMap ) },
currReqBase
);
request = sortQuery( request );
// Support: IE6
- // Append &* to satisfy load.php's WebRequest::checkUrlExtension test. This script
- // isn't actually used in IE6, but MediaWiki enforces it in general.
- addScript( sourceLoadScript + '?' + $.param( request ) + '&*', null, async );
+ // Append &* to satisfy load.php's WebRequest::checkUrlExtension test.
+ // This script isn't actually used in IE6, but MediaWiki enforces it in general.
+ addScript( sourceLoadScript + '?' + $.param( request ) + '&*' );
}
/**
var reqBase, splits, maxQueryLength, q, b, bSource, bGroup, bSourceGroup,
source, concatSource, origBatch, group, i, modules, sourceLoadScript,
currReqBase, currReqBaseLength, moduleMap, l,
- lastDotIndex, prefix, suffix, bytesAdded, async;
+ lastDotIndex, prefix, suffix, bytesAdded;
// Build a list of request parameters common to all requests.
reqBase = {
currReqBase.user = mw.config.get( 'wgUserName' );
}
currReqBaseLength = $.param( currReqBase ).length;
- async = true;
// We may need to split up the request to honor the query string length limit,
// so build it piece by piece.
l = currReqBaseLength + 9; // '&modules='.length == 9
if ( maxQueryLength > 0 && !$.isEmptyObject( moduleMap ) && l + bytesAdded > maxQueryLength ) {
// This request would become too long, create a new one
// and fire off the old one
- doRequest( moduleMap, currReqBase, sourceLoadScript, async );
+ doRequest( moduleMap, currReqBase, sourceLoadScript );
moduleMap = {};
- async = true;
l = currReqBaseLength + 9;
mw.track( 'resourceloader.splitRequest', { maxQueryLength: maxQueryLength } );
}
moduleMap[prefix] = [];
}
moduleMap[prefix].push( suffix );
- if ( !registry[modules[i]].async ) {
- // If this module is blocking, make the entire request blocking
- // This is slightly suboptimal, but in practice mixing of blocking
- // and async modules will only occur in debug mode.
- async = false;
- }
l += bytesAdded;
}
// If there's anything left in moduleMap, request that too
if ( !$.isEmptyObject( moduleMap ) ) {
- doRequest( moduleMap, currReqBase, sourceLoadScript, async );
+ doRequest( moduleMap, currReqBase, sourceLoadScript );
}
}
}
* @param {string} [type='text/javascript'] MIME type to use if calling with a URL of an
* external script or style; acceptable values are "text/css" and
* "text/javascript"; if no type is provided, text/javascript is assumed.
- * @param {boolean} [async] Whether to load modules asynchronously.
- * Ignored (and defaulted to `true`) if the document-ready event has already occurred.
- * Defaults to `true` if loading a URL, `false` otherwise.
*/
- load: function ( modules, type, async ) {
+ load: function ( modules, type ) {
var filtered, l;
// Validate input
// Allow calling with an external url or single dependency as a string
if ( typeof modules === 'string' ) {
if ( /^(https?:)?\/\//.test( modules ) ) {
- if ( async === undefined ) {
- // Assume async for bug 34542
- async = true;
- }
if ( type === 'text/css' ) {
// Support: IE 7-8
// Use properties instead of attributes as IE throws security
return;
}
if ( type === 'text/javascript' || type === undefined ) {
- addScript( modules, null, async );
+ addScript( modules );
return;
}
// Unknown type
return;
}
// Since some modules are not yet ready, queue up a request.
- request( filtered, undefined, undefined, async );
+ request( filtered, undefined, undefined );
},
/**
.mw-notification-title {
font-weight: bold;
}
+
+.mw-notification-type-warn {
+ border-color: #F5BE00; /* yellow */
+ background-color: #FFFFE8;
+}
+
+.mw-notification-type-error {
+ border-color: #EB3941; /* red */
+ background-color: #FFF8F8;
+}
}
}
+ if ( options.type ) {
+ // Sanitize options.type
+ options.type = options.type.replace( /[ _\-]+/g, '-' ).replace( /[^\-a-z0-9]+/ig, '' );
+ $notification.addClass( 'mw-notification-type-' + options.type );
+ }
+
if ( options.title ) {
$notificationTitle = $( '<div class="mw-notification-title"></div>' )
.text( options.title )
* - title:
* An optional title for the notification. Will be displayed above the
* content. Usually in bold.
+ *
+ * - type:
+ * An optional string for the type of the message used for styling:
+ * Examples: 'info', 'warn', 'error'.
*/
defaults: {
autoHide: true,
tag: false,
- title: undefined
+ title: undefined,
+ type: false
},
/**
while ( RLQ.length ) {
RLQ.shift()();
}
- RLQ = {
+ window.RLQ = {
push: function ( fn ) {
fn();
}
// Conditional script injection
if ( isCompatible() ) {
- document.write( $VARS.baseModulesScript );
+ ( function () {
+ var script = document.createElement( 'script' );
+ script.src = $VARS.baseModulesUri;
+ document.getElementsByTagName( 'head' )[0].appendChild( script );
+ }() );
+} else {
+ // Undo class swapping in case of an unsupported browser.
+ // See OutputPage::getHeadScripts().
+ document.documentElement.className = document.documentElement.className
+ .replace( /(^|\s)client-js(\s|$)/, '$1client-nojs$2' );
}
|</noinclude>style="color:red;"||Bar
!! endarticle
+!! article
+Template:table_attribs_6
+!! text
+style="background: <nowiki>
+
+
+red;</nowiki>" |
+!! endarticle
+
+!! article
+Template:table_attribs_7
+!! text
+<noinclude>
+|</noinclude>style{{=}}"background:#f9f9f9;"|Foo<ref>foo</ref>
+!! endarticle
+
!! article
Template:table_header_cells
!! text
Non-word characters are valid in extension tags (T19663)
!! wikitext
<tåg>tåg</tåg>
-!! html
+!! html/php
<pre>
'tåg'
array (
!! test
Isolated close tags should be treated as literal text (bug 52760)
+!! options
+parsoid=wt2html
!! wikitext
</b>
<s.foo>s</s>
-!! html+tidy
+!! html/php+tidy
+<p><s.foo>s</p>
+!! html/parsoid
<p><s.foo>s</p>
!! end
<nowiki> unordered list
!! wikitext
<nowiki>* This is not an unordered list item.</nowiki>
-!! html
+!! html/php
<p>* This is not an unordered list item.
</p>
+!! html/parsoid
+<p><span typeof="mw:Nowiki">* This is not an unordered list item.</span></p>
!! end
!! test
:and a colon
</nowiki>
-!! html
+!! html/php
<p>Lorem ipsum dolor
sed abit.
:and a colon
</p>
+!! html/parsoid
+<p><span typeof="mw:Nowiki">Lorem ipsum dolor
+
+sed abit.
+ sed nullum.
+
+:and a colon
+</span></p>
!! end
!! test
*There is not nowiki.
*There is <nowiki>nowiki</nowiki>.
-!! html
+!! html/php
<dl><dd>There is not nowiki.</dd>
<dd>There is nowiki.</dd></dl>
<ol><li>There is not nowiki.</li>
<ul><li>There is not nowiki.</li>
<li>There is nowiki.</li></ul>
+!! html/parsoid
+<dl><dd data-parsoid='{}'>There is not nowiki.</dd>
+<dd data-parsoid='{}'>There is <span typeof="mw:Nowiki">nowiki</span>.</dd></dl>
+
+<ol><li data-parsoid='{}'>There is not nowiki.</li>
+<li data-parsoid='{}'>There is <span typeof="mw:Nowiki">nowiki</span>.</li></ol>
+
+<ul><li data-parsoid='{}'>There is not nowiki.</li>
+<li data-parsoid='{}'>There is <span typeof="mw:Nowiki">nowiki</span>.</li></ul>
!! end
!! test
!! html
<p>* </nowiki> tag</p>
!! wikitext
-<nowiki>* </nowiki></nowiki> tag
+<nowiki>*</nowiki> <nowiki></nowiki></nowiki> tag
!! end
!! test
</nowiki>
</pre>
-!! html
+!! html/php
<pre>
<nowiki>
</pre>
</pre>
</p>
+!! html/parsoid
+<pre data-parsoid='{"stx":"html","strippedNL":true}'><nowiki>
+</pre>
+<p><span typeof="mw:Placeholder" data-parsoid='{"src":"</nowiki>"}'></nowiki></span>
+</pre></p>
+
+<p><span typeof="mw:Nowiki">
+<pre>
+<nowiki>
+</pre>
+</span>
+</pre></p>
!! end
!! test
'' no-italic ''
[[ NoLink ]]
</pre>
-!! html
+!! html/php
<pre>
* foo
# bar
[[ NoLink ]]
</pre>
+!! html/parsoid
+<pre data-parsoid='{"stx":"html","strippedNL":true}'>* foo
+# bar
+= no-h =
+'' no-italic ''
+[[ NoLink ]]
+</pre>
!!end
###
</dl>
</dd>
</dl>
+!! html/parsoid
+<dl><dt>a</dt><dd data-parsoid='{"stx":"row"}'>b</dd>
+<dt><b>a:b</b></dt>
+<dt><i data-parsoid='{"stx":"html"}'>a:b</i></dt>
+<dt><span data-parsoid='{"stx":"html"}'>a:b</span></dt>
+<dt><div data-parsoid='{"stx":"html"}'>a:b</div></dt>
+<dt><div data-parsoid='{"stx":"html","autoInsertedEnd":true}'>a</div></dt>
+<dd>b</dd>
+<dt><span about="#mwt1" typeof="mw:Transclusion" data-parsoid='{"pi":[[{"k":"1","spc":["","","",""]}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"a:b"}},"i":0}}]}'>a:b</span></dt>
+<dt><i about="#mwt2" typeof="mw:Transclusion" data-parsoid='{"pi":[[{"k":"1","spc":["","","",""]}]]}' data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"''a:b''"}},"i":0}}]}'>a:b</i>
+<dl><dt><dl><dt><i>a:b</i></dt></dl></dt></dl></dt></dl>
!! end
!! test
</tbody></table>
!! end
+## Edge case fix to prevent future regressions
+!! test
+T107652: <ref>s in templates that also generate table cell attributes should be rendered properly
+!! wikitext
+{|
+|{{table_attribs_7}}
+|}
+<references />
+!! html/parsoid
+<table>
+<tbody><tr><td style="background:#f9f9f9;" typeof="mw:Transclusion" about="#mwt1" data-mw='{"parts":["|",{"template":{"target":{"wt":"table_attribs_7","href":"./Template:Table_attribs_7"},"params":{},"i":0}}]}'>Foo<span class="mw-ref" id="cite_ref-1" rel="dc:references" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-1"},"attrs":{}}'><a href="#cite_note-1" style="counter-reset: mw-Ref 1;"><span class="mw-reflink-text">[1]</span></a></span></td></tr>
+</tbody></table>
+<ol class="mw-references" typeof="mw:Extension/references" about="#mwt5" data-mw='{"name":"references","attrs":{}}'><li about="#cite_note-1" id="cite_note-1"><a href="#cite_ref-1" rel="mw:referencedBy"><span class="mw-linkback-text">↑ </span></a> <span id="mw-reference-text-cite_note-1" class="mw-reference-text" data-parsoid="{}">foo</span></li></ol>
+!! end
+
!! test
Table with row followed by newlines and table heading
!! wikitext
<meta property="mw:PageProp/noeditsection" data-parsoid='{"magicSrc":"__NOEDITSECTION__"}'/>
!! end
+!!test
+__proto__ is treated as normal wikitext (T105997)
+!!wikitext
+__proto__
+!!html
+<p>__proto__
+</p>
+!!end
+
###
### Magic links
###
msgnw keyword
!! wikitext
{{msgnw:MSGNW test}}
-!! html
+!! html/php
<p>''None'' of '''this''' should be
* interpreted
 but rather passed unmodified
</tbody></table>
!! end
+# T107622
+!! test
+4. Entities and nowikis inside templated attributes should be handled correctly inside templated tables
+!! wikitext
+{|
+| {{table_attribs_6}} hi
+|}
+!! html/php
+<table>
+<tr>
+<td style="background: red;"> hi
+</td></tr></table>
+
+!! html/parsoid
+<table>
+<tbody><tr><td style="background: red;" typeof="mw:Transclusion" about="#mwt1" data-parsoid='{"autoInsertedEnd":true,"pi":[[]]}' data-mw='{"parts":["| ",{"template":{"target":{"wt":"table_attribs_6","href":"./Template:Table_attribs_6"},"params":{},"i":0}}," hi"]}'> hi</td></tr>
+</tbody></table>
+!! end
+
!!test
Templates: HTML Tables: 1. Generating start of a HTML table
!! wikitext
!! test
pre-save transform: subst:
!! options
-PST
+pst
!! wikitext
{{subst:test}}
-!! html
+!! html/php
This is a test template
!! end
!! test
pre-save transform: normal template
!! options
-PST
+pst
!! wikitext
{{test}}
-!! html
+!! html/php
{{test}}
!! end
!! test
pre-save transform: nonexistent template
!! options
-PST
+pst
!! wikitext
{{thistemplatedoesnotexist}}
-!! html
+!! html/php
{{thistemplatedoesnotexist}}
!! end
-
!! test
pre-save transform: subst magic variables
!! options
-PST
+pst
!! wikitext
{{subst:SITENAME}}
-!! html
+!! html/php
MediaWiki
!! end
pst
!! wikitext
{{subst:paramtest|param="something else"}}
-!! html
+!! html/php
This is a test template with parameter "something else"
!! end
pst
!! wikitext
{{subst:nowikitest}}
-!! html
+!! html/php
<nowiki>'''not wiki'''</nowiki>
!! end
-
!! article
Template:commenttest
!! text
pst
!! wikitext
{{subst:commenttest}}
-!! html
+!! html/php
This template has <!-- a comment --> in it.
!! end
pst noxml
!! wikitext
<nowiki>'''not wiki'''
-!! html
+!! html/php
<nowiki>'''not wiki'''
!! end
pst noxml
!! wikitext
<NOwiki>'''not wiki'''</noWIKI>
-!! html
+!! html/php
<NOwiki>'''not wiki'''</noWIKI>
!! end
pst noxml
!! wikitext
wiki<nowiki>nowiki<!--nowiki</nowiki>wiki
-!! html
+!! html/php
wiki<nowiki>nowiki<!--nowiki</nowiki>wiki
!!end
pst
!! wikitext
<!-- <gallery>data</gallery> -->
-!! html
+!! html/php
<!-- <gallery>data</gallery> -->
!!end
pst
!! wikitext
<!-- <tag>data</tag> -->
-!! html
+!! html/php
<!-- <tag>data</tag> -->
!!end
pst
!! wikitext
<!-- <nowiki>data</nowiki> -->
-!! html
+!! html/php
<!-- <nowiki>data</nowiki> -->
!!end
pst
!! wikitext
{{subst:Includes}}
-!! html
+!! html/php
Foobar
!! end
pst
!! wikitext
{{subst:Includes2}}
-!! html
+!! html/php
Foo
!! end
pst
!! wikitext
{{subst:SafeSubstTest}}{{safesubst:SubstTest}}
-!! html
+!! html/php
FoobarFoobar
!! end
[[|Article (context)]]
[[Bar:X (Y) Z|]]
[[:Bar:X (Y) Z|]]
-!! html
+!! html/php
[[Article (context)|Article]]
[[Bar:Article|Article]]
[[:Bar:Article|Article]]
[[:interwiki:Article|]]
[[interwiki:Bar:Article|]]
[[:interwiki:Bar:Article|]]
-!! html
+!! html/php
[[interwiki:Article|Article]]
[[:interwiki:Article|Article]]
[[interwiki:Bar:Article|Bar:Article]]
pst title=[[Somearticle (context)]]
!! wikitext
[[|Article]]
-!! html
+!! html/php
[[Article (context)|Article]]
!! end
[[|Otherplace]]
[[Otherplace, Elsewhere|]]
[[Otherplace, Elsewhere, Anywhere|]]
-!! html
+!! html/php
[[Otherplace, Somewhere|Otherplace]]
[[Otherplace, Elsewhere|Otherplace]]
[[Otherplace, Elsewhere, Anywhere|Otherplace]]
!! wikitext
[[|Otherplace]]
[[Otherplace (place), Elsewhere|]]
-!! html
+!! html/php
[[Otherplace, Somewhere|Otherplace]]
[[Otherplace (place), Elsewhere|Otherplace]]
!! end
!! wikitext
[[|Yes, you.]]
[[Me, Myself, and I (1937 song)|]]
-!! html
+!! html/php
[[Yes, you. (context)|Yes, you.]]
[[Me, Myself, and I (1937 song)|Me, Myself, and I]]
!! end
pst title=[[Ns:Somearticle]]
!! wikitext
[[|Article]]
-!! html
+!! html/php
[[Ns:Article|Article]]
!! end
pst title=[[Ns:Somearticle (context)]]
!! wikitext
[[|Article]]
-!! html
+!! html/php
[[Ns:Article (context)|Article]]
!! end
pst title=[[Ns:Somearticle, Context, Whatever]]
!! wikitext
[[|Article]]
-!! html
+!! html/php
[[Ns:Article, Context, Whatever|Article]]
!! end
pst title=[[Ns:Somearticle, Context (context)]]
!! wikitext
[[|Article]]
-!! html
+!! html/php
[[Ns:Article (context)|Article]]
!! end
pst title=[[Ns:Somearticle (IGNORED), Context]]
!! wikitext
[[|Article]]
-!! html
+!! html/php
[[Ns:Article, Context|Article]]
!! end
[[|Article(context)]]
[[Bar:X(Y)Z|]]
[[:Bar:X(Y)Z|]]
-!! html
+!! html/php
[[Article(context)|Article]]
[[Bar:Article(context)|Article]]
[[:Bar:Article(context)|Article]]
[[|Article (context)]]
[[Bar:X (Y) Z|]]
[[:Bar:X (Y) Z|]]
-!! html
+!! html/php
[[Article (context)|Article]]
[[Bar:Article (context)|Article]]
[[:Bar:Article (context)|Article]]
[[|Article(context)]]
[[Bar:X(Y)Z|]]
[[:Bar:X(Y)Z|]]
-!! html
+!! html/php
[[Article(context)|Article]]
[[Bar:Article(context)|Article]]
[[:Bar:Article(context)|Article]]
[[Bar:Article (context),context|]]
[[:Bar:Article (context), context|]]
[[:Bar:Article (context),context|]]
-!! html
+!! html/php
[[Article (context), context|Article]]
[[Article (context),context|Article]]
[[Bar:Article (context), context|Article]]
-!! html
+!! html/php
Empty lines are trimmed
!! end
* <noinclude>~~~</noinclude>
* <includeonly>~~~</includeonly>
* <onlyinclude>~~~</onlyinclude>
-!! html
+!! html/php
* [[Special:Contributions/127.0.0.1|127.0.0.1]]
* <noinclude>[[Special:Contributions/127.0.0.1|127.0.0.1]]</noinclude>
* <includeonly>[[Special:Contributions/127.0.0.1|127.0.0.1]]</includeonly>
But not inside includeonly
<includeonly>{{subst:Foo}}</includeonly>
-!! html
+!! html/php
Shall not expand:
<nowiki>~~~~</nowiki>
Parser hook: empty input
!! wikitext
<tag></tag>
-!! html
+!! html/php
<pre>
''
array (
Parser hook: empty input using terminated empty elements
!! wikitext
<tag/>
-!! html
+!! html/php
<pre>
NULL
array (
Parser hook: empty input using terminated empty elements (space before)
!! wikitext
<tag />
-!! html
+!! html/php
<pre>
NULL
array (
Parser hook: basic input
!! wikitext
<tag>input</tag>
-!! html
+!! html/php
<pre>
'input'
array (
Parser hook: case insensitive
!! wikitext
<TAG>input</TAG>
-!! html
+!! html/php
<pre>
'input'
array (
Parser hook: case insensitive, redux
!! wikitext
<TaG>input</TAg>
-!! html
+!! html/php
<pre>
'input'
array (
noxml
!! wikitext
<tag><tag></tag></tag>
-!! html
+!! html/php
<pre>
'<tag>'
array (
Parser hook: basic arguments
!! wikitext
<tag width=200 height = "100" depth = '50' square></tag>
-!! html
+!! html/php
<pre>
''
array (
Parser hook: argument containing a forward slash (bug 5344)
!! wikitext
<tag filename='/tmp/bla'></tag>
-!! html
+!! html/php
<pre>
''
array (
Parser hook: empty input using terminated empty elements (bug 2374)
!! wikitext
<tag foo=bar/>text
-!! html
+!! html/php
<pre>
NULL
array (
<tag width=200 height = "100" depth = '50' square/>
other stuff
</tag>
-!! html
+!! html/php
<pre>
NULL
array (
!! wikitext
<statictag>hello, world</statictag>
<statictag action=flush/>
-!! html
+!! html/php
<p>hello, world
</p>
!! end
!! wikitext
<!-- <statictag>hello, world</statictag> -->
<statictag action=flush/>
-!! html
+!! html/php
<p><br />
</p>
!! end
!! test
Sanitizer: Closing of closed but not open tags
+!! options
+parsoid=wt2html
!! wikitext
</s>
-!! html
-<p></s>
-</p>
+!! html/php+tidy
+!! html/parsoid
!! end
!! test
Sanitizer: Closing of closed but not open table tags
+!! options
+parsoid=wt2html
!! wikitext
Table not started</td></tr></table>
-!! html
-<p>Table not started</td></tr></table>
-</p>
+!! html/php+tidy
+<p>Table not started</p>
+!! html/parsoid
+<p>Table not started</p>
!! end
!! test
===bc===
==c==
===ca===
-!! html
+!! html/php
start
!! end
===bc===
==c==
===ca===
-!! html
+!! html/php
==a==
===aa===
====aaa====
===bc===
==c==
===ca===
-!! html
+!! html/php
===aa===
====aaa====
!! end
===bc===
==c==
===ca===
-!! html
+!! html/php
====aaa====
!! end
===bc===
==c==
===ca===
-!! html
+!! html/php
==b==
===ba===
===bb===
===bc===
==c==
===ca===
-!! html
+!! html/php
===ba===
!! end
===bc===
==c==
===ca===
-!! html
+!! html/php
===bb===
====bba====
!! end
===bc===
==c==
===ca===
-!! html
+!! html/php
====bba====
!! end
===bc===
==c==
===ca===
-!! html
+!! html/php
===bc===
!! end
===bc===
==c==
===ca===
-!! html
+!! html/php
==c==
===ca===
!! end
===bc===
==c==
===ca===
-!! html
+!! html/php
===ca===
!! end
===bc===
==c==
===ca===
-!! html
+!! html/php
!! end
!! test
==a==
==bogus== not a legal section
==b==
-!! html
+!! html/php
==a==
==bogus== not a legal section
!! end
==a==
==bogus== not a legal section
==b==
-!! html
+!! html/php
==b==
!! end
==a==
==b== <!-- -->
==c==
-!! html
+!! html/php
==a==
!! end
==a==
==b== <!-- -->
==c==
-!! html
+!! html/php
==b== <!-- -->
!! end
==a==
==bogus== <nowiki>not a legal section</nowiki>
==b==
-!! html
+!! html/php
==a==
==bogus== <nowiki>not a legal section</nowiki>
!! end
==a==
==bogus== <nowiki>not a legal section</nowiki>
==b==
-!! html
+!! html/php
==b==
!! end
-
# Formerly testing for bug 2587, now resolved by the use of unmarked sections
# instead of respecting commented sections
!! test
!! wikitext
<!-- -->==sec1==
==sec2==
-!! html
+!! html/php
==sec2==
!!end
!! wikitext
<!-- -->==sec1==
==sec2==
-!! html
+!! html/php
!!end
-
# Formerly testing for bug 2607, now resolved by the use of unmarked sections
# instead of respecting HTML-style headings
!! test
one
==2==
two
-!! html
+!! html/php
==1==
one
!! end
one
==2==
two
-!! html
+!! html/php
==2==
two
!! end
!! wikitext
<noinclude>==unmarked==</noinclude>
==marked==
-!! html
+!! html/php
==marked==
!!end
=== <!--
--> <!-- -->
But just in case it doesn't...
-!! html
+!! html/php
=== <!--
--> <!-- -->
But just in case it doesn't...
===bc===
==c==
===ca===
-!! html
+!! html/php
xxx
==a==
===bc===
==c==
===ca===
-!! html
+!! html/php
start
xxx
===bc===
==c==
===ca===
-!! html
+!! html/php
start
==a==
xxx
===bc===
==c==
===ca===
-!! html
+!! html/php
start
==a==
===aa===
===bc===
==c==
===ca===
-!! html
+!! html/php
start
==a==
===aa===
===bc===
==c==
===ca===
-!! html
+!! html/php
start
==a==
===aa===
===bc===
==c==
===ca===
-!! html
+!! html/php
start
==a==
===aa===
===bc===
==c==
===ca===
-!! html
+!! html/php
start
==a==
===aa===
===bc===
==c==
===ca===
-!! html
+!! html/php
start
==a==
===aa===
===bc===
==c==
===ca===
-!! html
+!! html/php
start
==a==
===aa===
===bc===
==c==
===ca===
-!! html
+!! html/php
start
==a==
===aa===
Preformatted initial line
==a==
===a===
-!! html
+!! html/php
Preformatted initial line
==a==
xxx
!! wikitext
==a==
a
-!! html
+!! html/php
==a==
a
!! end
!! wikitext
==a==
a
-!! html
+!! html/php
==a==
a
!! end
== Section Two ==
stuff
-!! html
+!! html/php
== Section Two ==
stuff
!! end
== Section Two ==
stuff
-!! html
+!! html/php
== Section One ==
<pre>
=======
!! end
-
!! test
Handling of 
 in URLs
!! wikitext
MSGNW magic word
!! wikitext
{{MSGNW:msg}}
-!! html
+!! html/php
<p>[[:Template:Msg]]
</p>
!! end
wgRestrictDisplayTitle=false
!! wikitext
this is not the the title
-!! html
+!! html/php
Parser test
<p>this is not the the title
</p>
!! wikitext
this is not the the title
{{DISPLAYTITLE:whatever}}
-!! html
+!! html/php
whatever
<p>this is not the the title
</p>
!! wikitext
this is not the the title
{{DISPLAYTITLE:whatever}}
-!! html
+!! html/php
Screen
<p>this is not the the title
</p>
!! wikitext
this is not the the title
{{DISPLAYTITLE:screen}}
-!! html
+!! html/php
screen
<p>this is not the the title
</p>
!! wikitext
this is not the the title
{{DISPLAYTITLE:screen}}
-!! html
+!! html/php
Screen
<p>this is not the the title
<a href="/index.php?title=Template:DISPLAYTITLE:screen&action=edit&redlink=1" class="new" title="Template:DISPLAYTITLE:screen (page does not exist)">Template:DISPLAYTITLE:screen</a>
wgAllowDisplayTitle=false
!! wikitext
this is not the the title
-!! html
+!! html/php
Screen
<p>this is not the the title
</p>
!! wikitext
this is not the the title
{{DISPLAYTITLE:<span style="display: none;">s</span>creen}}
-!! html
+!! html/php
<span style="/* attempt to bypass $wgRestrictDisplayTitle */">s</span>creen
<p>this is not the the title
</p>
!! wikitext
this is not the the title
{{DISPLAYTITLE:<span style="color: red;">s</span>creen}}
-!! html
+!! html/php
<span style="color: red;">s</span>creen
<p>this is not the the title
</p>
preload
!! wikitext
Hello <noinclude>cruel</noinclude><includeonly>kind</includeonly> world.
-!! html
+!! html/php
Hello kind world.
!! end
preload
!! wikitext
Goodbye <onlyinclude>Hello world</onlyinclude>
-!! html
+!! html/php
Hello world
!! end
preload
!! wikitext
<includeonly><</includeonly>includeonly>Hello world<includeonly><</includeonly>/includeonly>
-!! html
+!! html/php
<includeonly>Hello world</includeonly>
!! end
preload
!! wikitext
* <!-- Hello --> ''{{world}}'' {{<includeonly>subst:</includeonly>How are you}}{{ {{{|safesubst:}}} #if:1|2|3}}
-!! html
+!! html/php
* <!-- Hello --> ''{{world}}'' {{subst:How are you}}{{ {{{|safesubst:}}} #if:1|2|3}}
!! end
<small><figure class="mw-halign-right" typeof="mw:Image"><a href="./File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/300px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="34" width="300"/></a></figure></small>
!! end
+!! test
+3. Bad treebuilder fixup of formatting elt is cleaned up
+!! options
+parsoid=wt2html,wt2wt
+!! wikitext
+<small>'''foo[[File:Foobar.jpg|thumb|caption]]bar'''</small>
+!! html/parsoid
+<p><small><b>foo</b></small></p>
+<figure class="mw-default-size" typeof="mw:Image/Thumb"><a href="File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/220px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="25" width="220"/></a><figcaption><small><b>caption</b></small></figcaption></figure>
+<p><small><b>bar</b></small></p>
+!! end
+
+!! test
+4. Bad treebuilder fixup of formatting elt is cleaned up: formatting tags around captionless images are ignored
+!! options
+parsoid=wt2html,wt2wt
+!! wikitext
+'''<small>[[Image:Foobar.jpg|right|300px]]</small>'''
+!! html/parsoid
+<p><b><small></small></b></p>
+<figure class="mw-halign-right" typeof="mw:Image"><a href="File:Foobar.jpg"><img resource="./File:Foobar.jpg" src="//example.com/images/thumb/3/3a/Foobar.jpg/300px-Foobar.jpg" data-file-width="1941" data-file-height="220" data-file-type="bitmap" height="34" width="300"/></a></figure>
+<p></p>
+!! end
+
#### ----------------------------------------------------------------
#### Parsoid-only testing of Parsoid's impl of <ref> and <references>
#### tags. Parsoid's output for these tags differs from that of the
!! test
Headings: 6a. Heading chars in SOL context (with trailing spaces)
-!! options
-parsoid
!! wikitext
<nowiki>=a=</nowiki>
<nowiki>=a=</nowiki>
<nowiki>=a=</nowiki>
-!! html
-<p>=a=</p>
-<p>=a= </p>
-<p>=a= </p>
-<p>=a= </p>
+!! html/php
+<p>=a=
+</p><p>=a=
+</p><p>=a=
+</p><p>=a=
+</p>
+!! html/parsoid
+<p><span typeof="mw:Nowiki">=a=</span></p>
+
+<p><span typeof="mw:Nowiki">=a=</span></p>
+
+<p><span typeof="mw:Nowiki">=a=</span></p>
+
+<p><span typeof="mw:Nowiki">=a=</span></p>
!!end
!! test
Headings: 6b. Heading chars in SOL context (with trailing newlines)
-!! options
-parsoid
!! wikitext
<nowiki>=a=
b</nowiki>
<nowiki>=a=
b</nowiki>
-!! html
+!! html/php
<p>=a=
-b</p>
-<p>=a=
-b</p>
-<p>=a=
-b</p>
-<p>=a=
-b</p>
+b
+</p><p>=a=
+b
+</p><p>=a=
+b
+</p><p>=a=
+b
</p>
+!! html/parsoid
+<p><span typeof="mw:Nowiki">=a=
+b</span></p>
+
+<p><span typeof="mw:Nowiki">=a=
+b</span></p>
+
+<p><span typeof="mw:Nowiki">=a=
+b</span></p>
+
+<p><span typeof="mw:Nowiki">=a=
+b</span></p>
!!end
!! test
Headings: 6c. Heading chars in SOL context (leading newline break)
-!! options
-parsoid
!! wikitext
a
<nowiki>=b=</nowiki>
-!! html
+!! html/php
+<p>a
+=b=
+</p>
+!! html/parsoid
<p>a
-=b=</p>
+<span typeof="mw:Nowiki">=b=</span>
!!end
!! test
Headings: 6d. Heading chars in SOL context (with interspersed comments)
-!! options
-parsoid
!! wikitext
<!--c0--><nowiki>=a=</nowiki>
<!--c1--><nowiki>=a=</nowiki> <!--c2--> <!--c3-->
-!! html
-<p><!--c0-->=a=</p>
-<p><!--c1-->=a= <!--c2--> <!--c3--></p>
+!! html/php
+<p>=a=
+</p><p>=a=
+</p>
+!! html/parsoid
+<!--c0--><p><span typeof="mw:Nowiki">=a=</span></p>
+
+<!--c1--><p><span typeof="mw:Nowiki">=a=</span></p> <!--c2--> <!--c3-->
!!end
!! test
Headings: 6d. Heading chars in SOL context (No escaping needed)
!! options
parsoid=html2wt
-!! html
+!! html/parsoid
=a=<div>b</div>
!! wikitext
=a=<div>b</div>
Headings: 7. Insert a newline between new content and headings
!! options
parsoid=html2wt
-!! html
+!! html/parsoid
<h2>NEW</h2>
<p>new</p>
-<h2 data-parsoid='{"dsr":[0,5,2,2]}'>A</h2>
-<p data-parsoid='{"dsr":[6,7,0,0]}'>a</p>
+<h2 data-parsoid='{}'>A</h2>
+<p data-parsoid='{}'>a</p>
!! wikitext
== NEW ==
new
<nowiki>#</nowiki>foo
<nowiki>;Foo:</nowiki>bar
-!! html
+!! html/php
<p>*foo
</p><p>#foo
</p><p>;Foo:bar
</p>
+!! html/parsoid
+<p><span typeof="mw:Nowiki">*</span>foo</p>
+
+<p><span typeof="mw:Nowiki">#</span>foo</p>
+
+<p><span typeof="mw:Nowiki">;Foo:</span>bar</p>
!!end
!! test
!!test
Encapsulate protected attributes from wt
!! wikitext
-<div typeof="mw:placeholder stuff" data-parsoid="weird" data-parsoid-other="no" about="time" rel="mw:true">foo</div>
+<div typeof="mw:placeholder stuff" data-mw="whoo" data-parsoid="weird" data-parsoid-other="no" about="time" rel="mw:true">foo</div>
!! html/parsoid
-<body><div data-x-typeof="mw:placeholder stuff" data-x-data-parsoid="weird" data-x-data-parsoid-other="no" data-x-about="time" data-x-rel="mw:true">foo</div>
+<body><div data-x-typeof="mw:placeholder stuff" data-x-data-mw="whoo" data-x-data-parsoid="weird" data-x-data-parsoid-other="no" data-x-about="time" data-x-rel="mw:true">foo</div>
</body>
!!end
!! end
!! test
-Headings: Force sol-transparent links and behavior switches to serialize before/after
+1. Headings: Force sol-transparent links and behavior switches to serialize before/after
!! options
-parsoid=html2wt
+parsoid={
+ "modes": ["html2wt"],
+ "scrubWikitext": false
+}
+!! html
+<h2>hello there<link href="Category:A1" rel="mw:PageProp/Category" /></h2>
+<h2><link href="Category:A2" rel="mw:PageProp/Category" />hi pal</h2>
+
+<h2><!--foo--> <link href="Category:A3" rel="mw:PageProp/Category" /> how goes it</h2>
+<h2>it goes well <link href="Category:A4" rel="mw:PageProp/Category" /> <!--bar--></h2>
+
+<h2 data-parsoid='{}'>howdy<link href="Category:A5" rel="mw:PageProp/Category" /></h2>
+
+<h2><meta property="mw:PageProp/toc" /> ok</h2>
+!! wikitext
+== hello there [[Category:A1]] ==
+
+== [[Category:A2]] hi pal ==
+
+== <!--foo--> [[Category:A3]] how goes it ==
+
+== it goes well [[Category:A4]] <!--bar--> ==
+
+==howdy [[Category:A5]] ==
+
+== __TOC__ ok ==
+!! end
+
+!! test
+2. Headings: Force sol-transparent links and behavior switches to serialize before/after
+!! options
+parsoid={
+ "modes": ["html2wt"],
+ "scrubWikitext": true
+}
!! html
<h2>hello there<link href="Category:A1" rel="mw:PageProp/Category" /></h2>
<h2><link href="Category:A2" rel="mw:PageProp/Category" />hi pal</h2>
!! test
Headings: Don't hoist metas that come from templates
!! options
-parsoid=html2wt
+parsoid={
+ "modes": ["html2wt"],
+ "scrubWikitext": true
+}
!! html
<h2><span about="#mwt1" typeof="mw:Transclusion" data-parsoid="{}" data-mw='{"parts":[{"template":{"target":{"wt":"echo","href":"./Template:Echo"},"params":{"1":{"wt":"foo [[Category:Foo]]"}},"i":0}}]}'>foo </span><link rel="mw:PageProp/Category" href="./Category:Foo" about="#mwt1" data-parsoid="{}" /></h2>
!! wikitext
!! test
Headings: Category in ref isn't hoisted
!! options
-parsoid=html2wt
+parsoid={
+ "modes": ["html2wt"],
+ "scrubWikitext": true
+}
!! html
<h2> foo <span about="#mwt2" class="reference" id="cite_ref-1" rel="dc:references" typeof="mw:Extension/ref" data-mw='{"name":"ref","body":{"id":"mw-reference-text-cite_note-1"},"attrs":{}}'><a href="#cite_note-1">[1]</a></span> </h2>
baz</li>
<li>foo <b>bar</b>
baz</li></ul>
+
+<dl><dt>hi
+ho </dt><dd data-parsoid='{"stx":"row"}'> hi
+ho</dd></dl>
+
+<dl><dd> <table>
+<tbody><tr><td> ha
+ha
+ha</td></tr>
+</tbody></table></dd></dl>
!! wikitext
== testing 123 ==
* foo bar baz
* foo '''bar''' baz
+
+; hi ho : hi ho
+
+: {|
+| ha
+ha
+ha
+|}
!! end
!! test
x<nowiki/>http://cscott.net<nowiki/>x
!! end
+!! test
+WTS of edited autolink-like text (T103364)
+!! options
+parsoid={
+ "modes": ["wt2wt"],
+ "changes": [
+ [ "span[typeof]", "removeAttr", "typeof" ]
+ ]
+}
+!! wikitext
+Not a link: <nowiki>http://example.com</nowiki>.
+!! wikitext/edited
+Not a link: <span><nowiki>http://example.com</nowiki></span>.
+!! end
+
+!! test
+WTS of newly-authored autolink-like text (T103364)
+!! options
+parsoid=html2wt
+!! html/parsoid
+<p>http://example.com is not a link.</p>
+!! wikitext
+<nowiki>http://example.com is not a link.</nowiki>
+!! end
+
!! test
Edited Redirect link should emit a non-piped wikitext link
!! options
!! end
!! test
-Headings: Add space before/after == (T53744)
+1. Headings: Add space before/after == (T53744)
!! options
parsoid=html2wt
!! html
<h2> bar</h2>
<h2>baz </h2>
<h2><span> baz</span></h2>
-
-<!-- Even after hoisted content -->
-<h2> <link href="Category:A2" rel="mw:PageProp/Category" />ok</h2>
!! wikitext
== foo ==
== baz ==
== <span> baz</span> ==
+!! end
-<!-- Even after hoisted content -->
+!! test
+2. Headings: Add space before/after == even after hoisted content
+!! options
+parsoid={
+ "modes": ["html2wt"],
+ "scrubWikitext": true
+}
+!! html
+<h2> <link href="Category:A2" rel="mw:PageProp/Category" />ok</h2>
+!! wikitext
[[Category:A2]]
== ok ==
}
!! html
<p> hi</p>
+<p> hello</p>
!! wikitext
hi
+
+hello
!! end
!! test
parsoid=html2wt
!! html
<p> hi</p>
+<p> hello</p>
!! wikitext
<nowiki> </nowiki>hi
+
+<nowiki> </nowiki> hello
+!! end
+
+!! test
+3. Indent Pre Nowiki: suppress whitespace after newlines in new paragraph or table cell
+!! options
+parsoid={
+ "modes": ["html2wt"],
+ "scrubWikitext": true
+}
+!! html/parsoid
+<p>Foo
+ bar
+baz</p>
+
+<table><tr><td>Foo
+ bar
+ baz bang</td></tr></table>
+
+<p><!--boo--> foo
+ bar</p>
+
+<p> foo
+ bar<span>boo</span></p>
+!! wikitext
+Foo
+bar
+baz
+
+{|
+|Foo
+bar
+baz bang
+|}
+
+<!--boo-->foo
+bar
+
+foo
+bar<span>boo</span>
+!! end
+
+!! test
+1. New links that end in spaces
+!! options
+parsoid={
+ "modes": ["html2wt"],
+ "scrubWikitext": false
+}
+!! html
+<p><a rel="mw:WikiLink" href="./Berlin" title="Berlin">Berlin </a>is the capital of Germany.</p>
+<p><a rel="mw:WikiLink" href="./Foo" title="Foo">Foo </a><b>bar</b></p>
+!! wikitext
+[[Berlin ]]<nowiki/>is the capital of Germany.
+
+[[Foo ]]'''bar'''
+!! end
+
+!! test
+2. New links that end in spaces
+!! options
+parsoid={
+ "modes": ["html2wt"],
+ "scrubWikitext": true
+}
+!! html
+<p><a rel="mw:WikiLink" href="./Berlin" title="Berlin">Berlin </a>is the capital of Germany.</p>
+<p><a rel="mw:WikiLink" href="./Foo" title="Foo">Foo </a><b>bar</b></p>
+!! wikitext
+[[Berlin]] is the capital of Germany.
+
+[[Foo]] '''bar'''
+!! end
+
+!! test
+3. Existing links that end in spaces
+!! options
+parsoid={
+ "modes": ["html2wt"],
+ "scrubWikitext": true
+}
+!! html
+<p><a rel="mw:WikiLink" href="./Berlin" title="Berlin" data-parsoid='{"stx":"simple","a":{"href":"./Berlin"},"sa":{"href":"Berlin "}}'>Berlin </a>is the capital of Germany.</p>
+
+<p><a rel="mw:WikiLink" href="./Foo" title="Foo" data-parsoid='{"stx":"simple","a":{"href":"./Foo"},"sa":{"href":"Foo "}}'>Foo </a><b>bar</b></p>
+!! wikitext
+[[Berlin ]]<nowiki/>is the capital of Germany.
+
+[[Foo ]]'''bar'''
!! end
# ---------------------------------------------------
// Load module script only
array(
array( 'test.foo', ResourceLoaderModule::TYPE_SCRIPTS ),
- "<script>var RLQ = RLQ || []; RLQ.push( function () {\n"
- . 'document.write("\u003Cscript src=\"http://127.0.0.1:8080/w/load.php?'
- . 'debug=false\u0026amp;lang=en\u0026amp;modules=test.foo\u0026amp;only'
- . '=scripts\u0026amp;skin=fallback\u0026amp;*\"\u003E\u003C/script\u003E");'
+ "<script>window.RLQ = window.RLQ || []; window.RLQ.push( function () {\n"
+ . 'mw.loader.load("http://127.0.0.1:8080/w/load.php?debug=false\u0026lang=en\u0026modules=test.foo\u0026only=scripts\u0026skin=fallback\u0026*");'
. "\n} );</script>"
),
array(
// Don't condition wrap raw modules (like the startup module)
array( 'test.raw', ResourceLoaderModule::TYPE_SCRIPTS ),
- '<script src="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.raw&only=scripts&skin=fallback&*"></script>'
+ '<script async src="http://127.0.0.1:8080/w/load.php?debug=false&lang=en&modules=test.raw&only=scripts&skin=fallback&*"></script>'
),
// Load module styles only
// This also tests the order the modules are put into the url
// Load private module (only=scripts)
array(
array( 'test.quux', ResourceLoaderModule::TYPE_SCRIPTS ),
- "<script>var RLQ = RLQ || []; RLQ.push( function () {\n"
+ "<script>window.RLQ = window.RLQ || []; window.RLQ.push( function () {\n"
. "mw.test.baz({token:123});mw.loader.state({\"test.quux\":\"ready\"});\n"
. "\n} );</script>"
),
// Load private module (combined)
array(
array( 'test.quux', ResourceLoaderModule::TYPE_COMBINED ),
- "<script>var RLQ = RLQ || []; RLQ.push( function () {\n"
+ "<script>window.RLQ = window.RLQ || []; window.RLQ.push( function () {\n"
. "mw.loader.implement(\"test.quux\",function($,jQuery){"
. "mw.test.baz({token:123});},{\"css\":[\".mw-icon{transition:none}\\n"
. "\"]});\n\n} );</script>"
// Load two modules in separate groups
array(
array( array( 'test.group.foo', 'test.group.bar' ), ResourceLoaderModule::TYPE_COMBINED ),
- "<script>var RLQ = RLQ || []; RLQ.push( function () {\n"
- . 'document.write("\u003Cscript src=\"http://127.0.0.1:8080/w/load.php?debug=false\u0026amp;lang=en\u0026amp;modules=test.group.bar\u0026amp;skin=fallback\u0026amp;*\"\u003E\u003C/script\u003E");'
+ "<script>window.RLQ = window.RLQ || []; window.RLQ.push( function () {\n"
+ . 'mw.loader.load("http://127.0.0.1:8080/w/load.php?debug=false\u0026lang=en\u0026modules=test.group.bar\u0026skin=fallback\u0026*");'
. "\n} );</script>\n"
- . "<script>var RLQ = RLQ || []; RLQ.push( function () {\n"
- . 'document.write("\u003Cscript src=\"http://127.0.0.1:8080/w/load.php?debug=false\u0026amp;lang=en\u0026amp;modules=test.group.foo\u0026amp;skin=fallback\u0026amp;*\"\u003E\u003C/script\u003E");'
+ . "<script>window.RLQ = window.RLQ || []; window.RLQ.push( function () {\n"
+ . 'mw.loader.load("http://127.0.0.1:8080/w/load.php?debug=false\u0026lang=en\u0026modules=test.group.foo\u0026skin=fallback\u0026*");'
. "\n} );</script>"
),
);