Merge "Remove global state from DatabaseBase::__construct()"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 15 Sep 2016 17:09:34 +0000 (17:09 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 15 Sep 2016 17:09:34 +0000 (17:09 +0000)
21 files changed:
RELEASE-NOTES-1.28
autoload.php
includes/DummyLinker.php
includes/EditPage.php
includes/Linker.php
includes/OutputPage.php
includes/TemplatesOnThisPageFormatter.php [new file with mode: 0644]
includes/actions/InfoAction.php
includes/libs/objectcache/WinCacheBagOStuff.php
jsduck.json
languages/i18n/en.json
languages/messages/MessagesLzh.php
resources/Resources.php
resources/src/jquery/images/jquery.arrowSteps.divider-ltr.png [new file with mode: 0644]
resources/src/jquery/images/jquery.arrowSteps.divider-rtl.png [new file with mode: 0644]
resources/src/jquery/images/jquery.arrowSteps.head-ltr.png [new file with mode: 0644]
resources/src/jquery/images/jquery.arrowSteps.head-rtl.png [new file with mode: 0644]
resources/src/jquery/images/jquery.arrowSteps.tail-ltr.png [new file with mode: 0644]
resources/src/jquery/images/jquery.arrowSteps.tail-rtl.png [new file with mode: 0644]
resources/src/jquery/jquery.arrowSteps.css [new file with mode: 0644]
resources/src/jquery/jquery.arrowSteps.js [new file with mode: 0644]

index dfa482a..2accb99 100644 (file)
@@ -170,7 +170,6 @@ changes to languages because of Phabricator reports.
   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 ==
 
index d46d2ce..58fcff3 100644 (file)
@@ -1397,6 +1397,7 @@ $wgAutoloadLocalClasses = [
        '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',
index ba24799..89a735e 100644 (file)
@@ -453,12 +453,17 @@ class DummyLinker {
                );
        }
 
+       /**
+        * @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,
index 140cd72..9d329e8 100644 (file)
@@ -21,6 +21,7 @@
  */
 
 use MediaWiki\Logger\LoggerFactory;
+use MediaWiki\MediaWikiServices;
 
 /**
  * The edit page/HTML interface (split from Article)
@@ -747,8 +748,7 @@ class EditPage {
                $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' );
 
@@ -2752,8 +2752,7 @@ class EditPage {
 
                $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() ) ) );
@@ -2802,6 +2801,32 @@ class EditPage {
 
        }
 
+       /**
+        * 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.
         *
index bcc348e..eae49e7 100644 (file)
@@ -1919,6 +1919,8 @@ class Linker {
        }
 
        /**
+        * @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
@@ -1937,87 +1939,24 @@ class Linker {
        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 );
        }
 
        /**
index d9230b0..4c4fb1c 100644 (file)
@@ -2726,12 +2726,17 @@ class OutputPage extends ContextSource {
                        );
                        $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() );
diff --git a/includes/TemplatesOnThisPageFormatter.php b/includes/TemplatesOnThisPageFormatter.php
new file mode 100644 (file)
index 0000000..c0ae374
--- /dev/null
@@ -0,0 +1,183 @@
+<?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' ]
+               );
+       }
+
+}
index abc7cb2..4d80a1c 100644 (file)
@@ -631,14 +631,15 @@ class InfoAction extends FormlessAction {
                                        $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 )
                                ];
                        }
 
@@ -654,14 +655,15 @@ class InfoAction extends FormlessAction {
                                        $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 )
                                ];
                        }
                }
index 19cc66a..6996ce5 100644 (file)
@@ -65,6 +65,13 @@ class WinCacheBagOStuff extends BagOStuff {
        }
 
        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;
        }
 }
index ad95506..5b18365 100644 (file)
@@ -19,6 +19,7 @@
                "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",
index df45a7e..fc5871b 100644 (file)
@@ -28,7 +28,7 @@
        "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",
@@ -45,7 +45,7 @@
        "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",
index 6f91f1e..d9ef8bc 100644 (file)
@@ -18,6 +18,8 @@
  * @author Yanteng3
  */
 
+$fallback = 'zh-hant'; // T125373
+
 $specialPageAliases = [
        'Activeusers'               => [ '躍簿' ],
        'Allmessages'               => [ '官話' ],
index ff39537..63c3490 100644 (file)
@@ -165,6 +165,11 @@ return [
                ],
                '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',
        ],
diff --git a/resources/src/jquery/images/jquery.arrowSteps.divider-ltr.png b/resources/src/jquery/images/jquery.arrowSteps.divider-ltr.png
new file mode 100644 (file)
index 0000000..84ed2a2
Binary files /dev/null and b/resources/src/jquery/images/jquery.arrowSteps.divider-ltr.png differ
diff --git a/resources/src/jquery/images/jquery.arrowSteps.divider-rtl.png b/resources/src/jquery/images/jquery.arrowSteps.divider-rtl.png
new file mode 100644 (file)
index 0000000..c212aeb
Binary files /dev/null and b/resources/src/jquery/images/jquery.arrowSteps.divider-rtl.png differ
diff --git a/resources/src/jquery/images/jquery.arrowSteps.head-ltr.png b/resources/src/jquery/images/jquery.arrowSteps.head-ltr.png
new file mode 100644 (file)
index 0000000..e6546bf
Binary files /dev/null and b/resources/src/jquery/images/jquery.arrowSteps.head-ltr.png differ
diff --git a/resources/src/jquery/images/jquery.arrowSteps.head-rtl.png b/resources/src/jquery/images/jquery.arrowSteps.head-rtl.png
new file mode 100644 (file)
index 0000000..2af30b9
Binary files /dev/null and b/resources/src/jquery/images/jquery.arrowSteps.head-rtl.png differ
diff --git a/resources/src/jquery/images/jquery.arrowSteps.tail-ltr.png b/resources/src/jquery/images/jquery.arrowSteps.tail-ltr.png
new file mode 100644 (file)
index 0000000..3ad990b
Binary files /dev/null and b/resources/src/jquery/images/jquery.arrowSteps.tail-ltr.png differ
diff --git a/resources/src/jquery/images/jquery.arrowSteps.tail-rtl.png b/resources/src/jquery/images/jquery.arrowSteps.tail-rtl.png
new file mode 100644 (file)
index 0000000..1d3048e
Binary files /dev/null and b/resources/src/jquery/images/jquery.arrowSteps.tail-rtl.png differ
diff --git a/resources/src/jquery/jquery.arrowSteps.css b/resources/src/jquery/jquery.arrowSteps.css
new file mode 100644 (file)
index 0000000..d24fcc9
--- /dev/null
@@ -0,0 +1,45 @@
+.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;
+}
diff --git a/resources/src/jquery/jquery.arrowSteps.js b/resources/src/jquery/jquery.arrowSteps.js
new file mode 100644 (file)
index 0000000..b0c36c6
--- /dev/null
@@ -0,0 +1,98 @@
+/*!
+ * 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 ) );