phpunit.php: --regex and --keep-uploads. Instead of --regex, use --filter.
Instead of --keep-uploads, use the same option to parserTests.php, but you
must specify a directory with --upload-dir.
-* The 'jquery.arrowSteps' ResourceLoader module was removed.
== Compatibility ==
'TempFSFile' => __DIR__ . '/includes/filebackend/TempFSFile.php',
'TempFileRepo' => __DIR__ . '/includes/filerepo/FileRepo.php',
'TemplateParser' => __DIR__ . '/includes/TemplateParser.php',
+ 'TemplatesOnThisPageFormatter' => __DIR__ . '/includes/TemplatesOnThisPageFormatter.php',
'TestFileOpPerformance' => __DIR__ . '/maintenance/fileOpPerfTest.php',
'TextContent' => __DIR__ . '/includes/content/TextContent.php',
'TextContentHandler' => __DIR__ . '/includes/content/TextContentHandler.php',
);
}
+ /**
+ * @deprecated since 1.28, use TemplatesOnThisPageFormatter directly
+ */
public function formatTemplates(
$templates,
$preview = false,
$section = false,
$more = null
) {
+ wfDeprecated( __METHOD__, '1.28' );
+
return Linker::formatTemplates(
$templates,
$preview,
*/
use MediaWiki\Logger\LoggerFactory;
+use MediaWiki\MediaWikiServices;
/**
* The edit page/HTML interface (split from Article)
$this->showTextbox( $text, 'wpTextbox1', [ 'readonly' ] );
$wgOut->addHTML( $this->editFormTextAfterContent );
- $wgOut->addHTML( Html::rawElement( 'div', [ 'class' => 'templatesUsed' ],
- Linker::formatTemplates( $this->getTemplates() ) ) );
+ $wgOut->addHTML( $this->makeTemplatesOnThisPageList( $this->getTemplates() ) );
$wgOut->addModules( 'mediawiki.action.edit.collapsibleFooter' );
$wgOut->addHTML( $this->editFormTextAfterTools . "\n" );
- $wgOut->addHTML( Html::rawElement( 'div', [ 'class' => 'templatesUsed' ],
- Linker::formatTemplates( $this->getTemplates(), $this->preview, $this->section != '' ) ) );
+ $wgOut->addHTML( $this->makeTemplatesOnThisPageList( $this->getTemplates() ) );
$wgOut->addHTML( Html::rawElement( 'div', [ 'class' => 'hiddencats' ],
Linker::formatHiddenCategories( $this->page->getHiddenCategories() ) ) );
}
+ /**
+ * Wrapper around TemplatesOnThisPageFormatter to make
+ * a "templates on this page" list.
+ *
+ * @param Title[] $templates
+ * @return string HTML
+ */
+ protected function makeTemplatesOnThisPageList( array $templates ) {
+ $templateListFormatter = new TemplatesOnThisPageFormatter(
+ $this->context, MediaWikiServices::getInstance()->getLinkRenderer()
+ );
+
+ // preview if preview, else section if section, else false
+ $type = false;
+ if ( $this->preview ) {
+ $type = 'preview';
+ } elseif ( $this->section != '' ) {
+ $type = 'section';
+ }
+
+ return Html::rawElement( 'div', [ 'class' => 'templatesUsed' ],
+ $templateListFormatter->format( $templates, $type )
+ );
+
+ }
+
/**
* Extract the section title from current section text, if any.
*
}
/**
+ * @deprecated since 1.28, use TemplatesOnThisPageFormatter directly
+ *
* Returns HTML for the "templates used on this page" list.
*
* Make an HTML list of templates, and then add a "More..." link at
public static function formatTemplates( $templates, $preview = false,
$section = false, $more = null
) {
- global $wgLang;
-
- $outText = '';
- if ( count( $templates ) > 0 ) {
- # Do a batch existence check
- $batch = new LinkBatch;
- foreach ( $templates as $title ) {
- $batch->addObj( $title );
- }
- $batch->execute();
-
- # Construct the HTML
- $outText = '<div class="mw-templatesUsedExplanation">';
- if ( $preview ) {
- $outText .= wfMessage( 'templatesusedpreview' )->numParams( count( $templates ) )
- ->parseAsBlock();
- } elseif ( $section ) {
- $outText .= wfMessage( 'templatesusedsection' )->numParams( count( $templates ) )
- ->parseAsBlock();
- } else {
- $outText .= wfMessage( 'templatesused' )->numParams( count( $templates ) )
- ->parseAsBlock();
- }
- $outText .= "</div><ul>\n";
-
- usort( $templates, 'Title::compare' );
- foreach ( $templates as $titleObj ) {
- $protected = '';
- $restrictions = $titleObj->getRestrictions( 'edit' );
- if ( $restrictions ) {
- // Check backwards-compatible messages
- $msg = null;
- if ( $restrictions === [ 'sysop' ] ) {
- $msg = wfMessage( 'template-protected' );
- } elseif ( $restrictions === [ 'autoconfirmed' ] ) {
- $msg = wfMessage( 'template-semiprotected' );
- }
- if ( $msg && !$msg->isDisabled() ) {
- $protected = $msg->parse();
- } else {
- // Construct the message from restriction-level-*
- // e.g. restriction-level-sysop, restriction-level-autoconfirmed
- $msgs = [];
- foreach ( $restrictions as $r ) {
- $msgs[] = wfMessage( "restriction-level-$r" )->parse();
- }
- $protected = wfMessage( 'parentheses' )
- ->rawParams( $wgLang->commaList( $msgs ) )->escaped();
- }
- }
- if ( $titleObj->quickUserCan( 'edit' ) ) {
- $editLink = self::link(
- $titleObj,
- wfMessage( 'editlink' )->escaped(),
- [],
- [ 'action' => 'edit' ]
- );
- } else {
- $editLink = self::link(
- $titleObj,
- wfMessage( 'viewsourcelink' )->escaped(),
- [],
- [ 'action' => 'edit' ]
- );
- }
- $outText .= '<li>' . self::link( $titleObj )
- . wfMessage( 'word-separator' )->escaped()
- . wfMessage( 'parentheses' )->rawParams( $editLink )->escaped()
- . wfMessage( 'word-separator' )->escaped()
- . $protected . '</li>';
- }
+ wfDeprecated( __METHOD__, '1.28' );
- if ( $more instanceof Title ) {
- $outText .= '<li>' . self::link( $more, wfMessage( 'moredotdotdot' ) ) . '</li>';
- } elseif ( $more ) {
- $outText .= "<li>$more</li>";
- }
+ $type = false;
+ if ( $preview ) {
+ $type = 'preview';
+ } elseif ( $section ) {
+ $type = 'section';
+ }
- $outText .= '</ul>';
+ if ( $more instanceof Message ) {
+ $more = $more->toString();
}
- return $outText;
+
+ $formatter = new TemplatesOnThisPageFormatter(
+ RequestContext::getMain(),
+ MediaWikiServices::getInstance()->getLinkRenderer()
+ );
+ return $formatter->format( $templates, $type, $more );
}
/**
);
$this->rlExemptStyleModules = $exemptGroups;
- // Manually handled by getBottomScripts()
- $userModule = $rl->getModule( 'user' );
- $userState = $userModule->isKnownEmpty( $context ) && !$this->isUserJsPreview()
- ? 'ready'
- : 'loading';
- $this->rlUserModuleState = $exemptStates['user'] = $userState;
+ $isUserModuleFiltered = !$this->filterModules( [ 'user' ] );
+ // If this page filters out 'user', makeResourceLoaderLink will drop it.
+ // Avoid indefinite "loading" state or untrue "ready" state (T145368).
+ if ( !$isUserModuleFiltered ) {
+ // Manually handled by getBottomScripts()
+ $userModule = $rl->getModule( 'user' );
+ $userState = $userModule->isKnownEmpty( $context ) && !$this->isUserJsPreview()
+ ? 'ready'
+ : 'loading';
+ $this->rlUserModuleState = $exemptStates['user'] = $userState;
+ }
$rlClient = new ResourceLoaderClientHtml( $context, $this->getTarget() );
$rlClient->setConfig( $this->getJSVars() );
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+use MediaWiki\Linker\LinkRenderer;
+use MediaWiki\Linker\LinkTarget;
+
+/**
+ * Handles formatting for the "templates used on this page"
+ * lists. Formerly known as Linker::formatTemplates()
+ *
+ * @since 1.28
+ */
+class TemplatesOnThisPageFormatter {
+
+ /**
+ * @var IContextSource
+ */
+ private $context;
+
+ /**
+ * @var LinkRenderer
+ */
+ private $linkRenderer;
+
+ /**
+ * @param IContextSource $context
+ * @param LinkRenderer $linkRenderer
+ */
+ public function __construct( IContextSource $context, LinkRenderer $linkRenderer ) {
+ $this->context = $context;
+ $this->linkRenderer = $linkRenderer;
+ }
+
+ /**
+ * Make an HTML list of templates, and then add a "More..." link at
+ * the bottom. If $more is null, do not add a "More..." link. If $more
+ * is a LinkTarget, make a link to that title and use it. If $more is a string,
+ * directly paste it in as the link (escaping needs to be done manually).
+ *
+ * @param LinkTarget[] $templates
+ * @param string|bool $type 'preview' if a preview, 'section' if a section edit, false if neither
+ * @param LinkTarget|string|null $more An escaped link for "More..." of the templates
+ * @return string HTML output
+ */
+ public function format( array $templates, $type = false, $more = null ) {
+ if ( !$templates ) {
+ // No templates
+ return '';
+ }
+
+ # Do a batch existence check
+ $batch = new LinkBatch;
+ foreach ( $templates as $title ) {
+ $batch->addObj( $title );
+ }
+ $batch->execute();
+
+ # Construct the HTML
+ $outText = '<div class="mw-templatesUsedExplanation">';
+ $count = count( $templates );
+ if ( $type === 'preview' ) {
+ $outText .= $this->context->msg( 'templatesusedpreview' )->numParams( $count )
+ ->parseAsBlock();
+ } elseif ( $type === 'section' ) {
+ $outText .= $this->context->msg( 'templatesusedsection' )->numParams( $count )
+ ->parseAsBlock();
+ } else {
+ $outText .= $this->context->msg( 'templatesused' )->numParams( $count )
+ ->parseAsBlock();
+ }
+ $outText .= "</div><ul>\n";
+
+ usort( $templates, 'Title::compare' );
+ foreach ( $templates as $template ) {
+ $outText .= $this->formatTemplate( $template );
+ }
+
+ if ( $more instanceof LinkTarget ) {
+ $outText .= Html::rawElement( 'li', $this->linkRenderer->makeLink(
+ $more, $this->context->msg( 'moredotdotdot' )->text() ) );
+ } elseif ( $more ) {
+ // Documented as should already be escaped
+ $outText .= Html::rawElement( 'li', $more );
+ }
+
+ $outText .= '</ul>';
+ return $outText;
+ }
+
+ /**
+ * Builds an <li> item for an individual template
+ *
+ * @param LinkTarget $target
+ * @return string
+ */
+ private function formatTemplate( LinkTarget $target ) {
+ // TODO Would be nice if we didn't have to use Title here
+ $titleObj = Title::newFromLinkTarget( $target );
+ $protected = $this->getRestrictionsText( $titleObj->getRestrictions( 'edit' ) );
+ $editLink = $this->buildEditLink( $titleObj );
+ return '<li>' . $this->linkRenderer->makeLink( $target )
+ . $this->context->msg( 'word-separator' )->escaped()
+ . $this->context->msg( 'parentheses' )->rawParams( $editLink )->escaped()
+ . $this->context->msg( 'word-separator' )->escaped()
+ . $protected . '</li>';
+ }
+
+ /**
+ * If the page is protected, get the relevant text
+ * for those restrictions
+ *
+ * @param array $restrictions
+ * @return string
+ */
+ private function getRestrictionsText( array $restrictions ) {
+ $protected = '';
+ if ( !$restrictions ) {
+ return $protected;
+ }
+
+ // Check backwards-compatible messages
+ $msg = null;
+ if ( $restrictions === [ 'sysop' ] ) {
+ $msg = $this->context->msg( 'template-protected' );
+ } elseif ( $restrictions === [ 'autoconfirmed' ] ) {
+ $msg = $this->context->msg( 'template-semiprotected' );
+ }
+ if ( $msg && !$msg->isDisabled() ) {
+ $protected = $msg->parse();
+ } else {
+ // Construct the message from restriction-level-*
+ // e.g. restriction-level-sysop, restriction-level-autoconfirmed
+ $msgs = [];
+ foreach ( $restrictions as $r ) {
+ $msgs[] = $this->context->msg( "restriction-level-$r" )->parse();
+ }
+ $protected = $this->context->msg( 'parentheses' )
+ ->rawParams( $this->context->getLanguage()->commaList( $msgs ) )->escaped();
+ }
+
+ return $protected;
+ }
+
+ /**
+ * Return a link to the edit page, with the text
+ * saying "view source" if the user can't edit the page
+ *
+ * @param Title $titleObj
+ * @return string
+ */
+ private function buildEditLink( Title $titleObj ) {
+ if ( $titleObj->quickUserCan( 'edit', $this->context->getUser() ) ) {
+ $linkMsg = 'editlink';
+ } else {
+ $linkMsg = 'viewsourcelink';
+ }
+
+ return $this->linkRenderer->makeLink(
+ $titleObj,
+ $this->context->msg( $linkMsg )->text(),
+ [],
+ [ 'action' => 'edit' ]
+ );
+ }
+
+}
$more = null;
}
+ $templateListFormatter = new TemplatesOnThisPageFormatter(
+ $this->getContext(),
+ $linkRenderer
+ );
+
$pageInfo['header-properties'][] = [
$this->msg( 'pageinfo-templates' )
->numParams( $pageCounts['transclusion']['from'] ),
- Linker::formatTemplates(
- $transcludedTemplates,
- false,
- false,
- $more )
+ $templateListFormatter->format( $transcludedTemplates, false, $more )
];
}
$more = null;
}
+ $templateListFormatter = new TemplatesOnThisPageFormatter(
+ $this->getContext(),
+ $linkRenderer
+ );
+
$pageInfo['header-properties'][] = [
$this->msg( 'pageinfo-transclusions' )
->numParams( $pageCounts['transclusion']['to'] ),
- Linker::formatTemplates(
- $transcludedTargets,
- false,
- false,
- $more )
+ $templateListFormatter->format( $transcludedTargets, false, $more )
];
}
}
}
public function merge( $key, callable $callback, $exptime = 0, $attempts = 10, $flags = 0 ) {
- return $this->mergeViaCas( $key, $callback, $exptime, $attempts );
+ if ( wincache_lock( $key ) ) { // optimize with FIFO lock
+ $ok = $this->mergeViaLock( $key, $callback, $exptime, $attempts, $flags );
+ wincache_unlock( $key );
+ } else {
+ $ok = false;
+ }
+
+ return $ok;
}
}
"resources/src/mediawiki.toolbar",
"resources/src/mediawiki.widgets",
"resources/src/jquery/jquery.accessKeyLabel.js",
+ "resources/src/jquery/jquery.arrowSteps.js",
"resources/src/jquery/jquery.autoEllipsis.js",
"resources/src/jquery/jquery.badge.js",
"resources/src/jquery/jquery.byteLength.js",
"tog-enotifminoredits": "Email me also for minor edits of pages and files",
"tog-enotifrevealaddr": "Reveal my email address in notification emails",
"tog-shownumberswatching": "Show the number of watching users",
- "tog-oldsig": "Existing signature:",
+ "tog-oldsig": "Your existing signature:",
"tog-fancysig": "Treat signature as wikitext (without an automatic link)",
"tog-uselivepreview": "Use live preview",
"tog-forceeditsummary": "Prompt me when entering a blank edit summary",
"tog-showhiddencats": "Show hidden categories",
"tog-norollbackdiff": "Don't show diff after performing a rollback",
"tog-useeditwarning": "Warn me when I leave an edit page with unsaved changes",
- "tog-prefershttps": "Always use a secure connection when logged in",
+ "tog-prefershttps": "Always use a secure connection while logged in",
"underline-always": "Always",
"underline-never": "Never",
"underline-default": "Skin or browser default",
"category-file-count-limited": "The following {{PLURAL:$1|file is|$1 files are}} in the current category.",
"listingcontinuesabbrev": "cont.",
"index-category": "Indexed pages",
- "noindex-category": "Noindexed pages",
+ "noindex-category": "Non-indexed pages",
"broken-file-category": "Pages with broken file links",
"categoryviewer-pagedlinks": "($1) ($2)",
"category-header-numerals": "$1–$2",
"newwindow": "(opens in new window)",
"cancel": "Cancel",
"moredotdotdot": "More...",
- "morenotlisted": "This list is not complete.",
+ "morenotlisted": "This list may be incomplete.",
"mypage": "Page",
"mytalk": "Talk",
"anontalk": "Talk",
* @author Yanteng3
*/
+$fallback = 'zh-hant'; // T125373
+
$specialPageAliases = [
'Activeusers' => [ '躍簿' ],
'Allmessages' => [ '官話' ],
],
'scripts' => 'resources/lib/jquery/jquery.appear.js',
],
+ 'jquery.arrowSteps' => [
+ 'scripts' => 'resources/src/jquery/jquery.arrowSteps.js',
+ 'styles' => 'resources/src/jquery/jquery.arrowSteps.css',
+ 'targets' => [ 'desktop', 'mobile' ],
+ ],
'jquery.async' => [
'scripts' => 'resources/lib/jquery/jquery.async.js',
],
--- /dev/null
+.arrowSteps {
+ list-style-type: none;
+ list-style-image: none;
+ border: 1px solid #666;
+ position: relative;
+}
+
+.arrowSteps li {
+ float: left;
+ padding: 0px;
+ margin: 0px;
+ border: 0 none;
+}
+
+.arrowSteps li div {
+ padding: 0.5em;
+ text-align: center;
+ white-space: nowrap;
+ overflow: hidden;
+}
+
+.arrowSteps li.arrow div {
+ /* @embed */
+ background: url( images/jquery.arrowSteps.divider-ltr.png ) no-repeat right center;
+}
+
+/* applied to the element preceding the highlighted step */
+.arrowSteps li.arrow.tail div {
+ /* @embed */
+ background: url( images/jquery.arrowSteps.tail-ltr.png ) no-repeat right center;
+}
+
+/* this applies to all highlighted, including the last */
+.arrowSteps li.head div {
+ /* @embed */
+ background: url( images/jquery.arrowSteps.head-ltr.png ) no-repeat left center;
+ font-weight: bold;
+}
+
+/* this applies to all highlighted arrows except the last */
+.arrowSteps li.arrow.head div {
+ /* TODO: eliminate duplication of jquery.arrowSteps.head.png embedding */
+ /* @embed */
+ background: url( images/jquery.arrowSteps.head-ltr.png ) no-repeat right center;
+}
--- /dev/null
+/*!
+ * jQuery arrowSteps plugin
+ * Copyright Neil Kandalgaonkar, 2010
+ *
+ * This work is licensed under the terms of the GNU General Public License,
+ * version 2 or later.
+ * (see http://www.fsf.org/licensing/licenses/gpl.html).
+ * Derivative works and later versions of the code must be free software
+ * licensed under the same or a compatible license.
+ */
+
+/**
+ * @class jQuery.plugin.arrowSteps
+ */
+( function ( $ ) {
+ /**
+ * Show users their progress through a series of steps, via a row of items that fit
+ * together like arrows. One item can be highlighted at a time.
+ *
+ * <ul id="robin-hood-daffy">
+ * <li id="guard"><div>Guard!</div></li>
+ * <li id="turn"><div>Turn!</div></li>
+ * <li id="parry"><div>Parry!</div></li>
+ * <li id="dodge"><div>Dodge!</div></li>
+ * <li id="spin"><div>Spin!</div></li>
+ * <li id="ha"><div>Ha!</div></li>
+ * <li id="thrust"><div>Thrust!</div></li>
+ * </ul>
+ *
+ * <script>
+ * $( '#robin-hood-daffy' ).arrowSteps();
+ * </script>
+ *
+ * @return {jQuery}
+ * @chainable
+ */
+ $.fn.arrowSteps = function () {
+ var $steps, width, arrowWidth, $stepDiv,
+ $el = this,
+ paddingSide = $( 'body' ).hasClass( 'rtl' ) ? 'padding-left' : 'padding-right';
+
+ $el.addClass( 'arrowSteps' );
+ $steps = $el.find( 'li' );
+
+ width = parseInt( 100 / $steps.length, 10 );
+ $steps.css( 'width', width + '%' );
+
+ // Every step except the last one has an arrow pointing forward:
+ // at the right hand side in LTR languages, and at the left hand side in RTL.
+ // Also add in the padding for the calculated arrow width.
+ $stepDiv = $steps.filter( ':not(:last-child)' ).addClass( 'arrow' ).find( 'div' );
+
+ // Execute when complete page is fully loaded, including all frames, objects and images
+ $( window ).on( 'load', function () {
+ arrowWidth = parseInt( $el.outerHeight(), 10 );
+ $stepDiv.css( paddingSide, arrowWidth.toString() + 'px' );
+ } );
+
+ $el.data( 'arrowSteps', $steps );
+
+ return this;
+ };
+
+ /**
+ * Highlights the element selected by the selector.
+ *
+ * $( '#robin-hood-daffy' ).arrowStepsHighlight( '#guard' );
+ * // 'Guard!' is highlighted.
+ *
+ * // ... user completes the 'guard' step ...
+ *
+ * $( '#robin-hood-daffy' ).arrowStepsHighlight( '#turn' );
+ * // 'Turn!' is highlighted.
+ *
+ * @param {string} selector
+ */
+ $.fn.arrowStepsHighlight = function ( selector ) {
+ var $previous,
+ $steps = this.data( 'arrowSteps' );
+ $.each( $steps, function ( i, step ) {
+ var $step = $( step );
+ if ( $step.is( selector ) ) {
+ if ( $previous ) {
+ $previous.addClass( 'tail' );
+ }
+ $step.addClass( 'head' );
+ } else {
+ $step.removeClass( 'head tail lasthead' );
+ }
+ $previous = $step;
+ } );
+ };
+
+ /**
+ * @class jQuery
+ * @mixins jQuery.plugin.arrowSteps
+ */
+}( jQuery ) );