Merge "SpecialWatchlist: Remove random unnecessary `<hr>`"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Fri, 4 Dec 2015 17:26:33 +0000 (17:26 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 4 Dec 2015 17:26:33 +0000 (17:26 +0000)
autoload.php
includes/DefaultSettings.php
includes/Preferences.php
includes/PrefixSearch.php
includes/search/SearchExactMatchRescorer.php [new file with mode: 0644]
includes/specials/SpecialProtectedpages.php
includes/specials/SpecialWatchlist.php
languages/i18n/en.json
languages/i18n/qqq.json
resources/Resources.php
resources/src/mediawiki.special/mediawiki.special.watchlist.js [new file with mode: 0644]

index 3e5c1b5..84bfe0b 100644 (file)
@@ -1089,6 +1089,7 @@ $wgAutoloadLocalClasses = array(
        'SearchDump' => __DIR__ . '/maintenance/dumpIterator.php',
        'SearchEngine' => __DIR__ . '/includes/search/SearchEngine.php',
        'SearchEngineDummy' => __DIR__ . '/includes/search/SearchEngine.php',
+       'SearchExactMatchRescorer' => __DIR__ . '/includes/search/SearchExactMatchRescorer.php',
        'SearchHighlighter' => __DIR__ . '/includes/search/SearchHighlighter.php',
        'SearchMssql' => __DIR__ . '/includes/search/SearchMssql.php',
        'SearchMySQL' => __DIR__ . '/includes/search/SearchMySQL.php',
index edc54f2..9d93797 100644 (file)
@@ -4590,6 +4590,7 @@ $wgDefaultUserOptions = array(
        'watchlisthideown' => 0,
        'watchlisthidepatrolled' => 0,
        'watchlisthidecategorization' => 1,
+       'watchlistreloadautomatically' => 0,
        'watchmoves' => 0,
        'watchrollback' => 0,
        'wllimit' => 250,
index 096f8e3..c7ab9cd 100644 (file)
@@ -1006,6 +1006,11 @@ class Preferences {
                        'section' => 'watchlist/advancedwatchlist',
                        'label-message' => 'tog-watchlisthideliu',
                );
+               $defaultPreferences['watchlistreloadautomatically'] = array(
+                       'type' => 'toggle',
+                       'section' => 'watchlist/advancedwatchlist',
+                       'label-message' => 'tog-watchlistreloadautomatically',
+               );
 
                if ( $config->get( 'RCWatchCategoryMembership' ) ) {
                        $defaultPreferences['watchlisthidecategorization'] = array(
index e328e9f..c6f187d 100644 (file)
@@ -164,112 +164,9 @@ abstract class PrefixSearch {
                return $this->strings( $this->handleResultFromHook( $srchres, $namespaces, $search, $limit ) );
        }
 
-       /**
-        * Default search backend does proper prefix searching, but custom backends
-        * may sort based on other algorythms that may cause the exact title match
-        * to not be in the results or be lower down the list.
-        * @param array $srchres results from the hook
-        * @return array munged results from the hook
-        */
        private function handleResultFromHook( $srchres, $namespaces, $search, $limit ) {
-               // Pick namespace (based on PrefixSearch::defaultSearchBackend)
-               $ns = in_array( NS_MAIN, $namespaces ) ? NS_MAIN : $namespaces[0];
-               $t = Title::newFromText( $search, $ns );
-               if ( !$t || !$t->exists() ) {
-                       // No exact match so just return the search results
-                       return $srchres;
-               }
-               $string = $t->getPrefixedText();
-               $key = array_search( $string, $srchres );
-               if ( $key !== false ) {
-                       // Exact match was in the results so just move it to the front
-                       return $this->pullFront( $key, $srchres );
-               }
-               // Exact match not in the search results so check for some redirect handling cases
-               if ( $t->isRedirect() ) {
-                       $target = $this->getRedirectTarget( $t );
-                       $key = array_search( $target, $srchres );
-                       if ( $key !== false ) {
-                               // Exact match is a redirect to one of the returned matches so pull the
-                               // returned match to the front.  This might look odd but the alternative
-                               // is to put the redirect in front and drop the match.  The name of the
-                               // found match is often more descriptive/better formed than the name of
-                               // the redirect AND by definition they share a prefix.  Hopefully this
-                               // choice is less confusing and more helpful.  But it might not be.  But
-                               // it is the choice we're going with for now.
-                               return $this->pullFront( $key, $srchres );
-                       }
-                       $redirectTargetsToRedirect = $this->redirectTargetsToRedirect( $srchres );
-                       if ( isset( $redirectTargetsToRedirect[$target] ) ) {
-                               // The exact match and something in the results list are both redirects
-                               // to the same thing!  In this case we'll pull the returned match to the
-                               // top following the same logic above.  Again, it might not be a perfect
-                               // choice but it'll do.
-                               return $this->pullFront( $redirectTargetsToRedirect[$target], $srchres );
-                       }
-               } else {
-                       $redirectTargetsToRedirect = $this->redirectTargetsToRedirect( $srchres );
-                       if ( isset( $redirectTargetsToRedirect[$string] ) ) {
-                               // The exact match is the target of a redirect already in the results list so remove
-                               // the redirect from the results list and push the exact match to the front
-                               array_splice( $srchres, $redirectTargetsToRedirect[$string], 1 );
-                               array_unshift( $srchres, $string );
-                               return $srchres;
-                       }
-               }
-
-               // Exact match is totally unique from the other results so just add it to the front
-               array_unshift( $srchres, $string );
-               // And roll one off the end if the results are too long
-               if ( count( $srchres ) > $limit ) {
-                       array_pop( $srchres );
-               }
-               return $srchres;
-       }
-
-       /**
-        * @param Array(string) $titles as strings
-        * @return Array(string => int) redirect target prefixedText to index of title in titles
-        *   that is a redirect to it.
-        */
-       private function redirectTargetsToRedirect( $titles ) {
-               $result = array();
-               foreach ( $titles as $key => $titleText ) {
-                       $title = Title::newFromText( $titleText );
-                       if ( !$title || !$title->isRedirect() ) {
-                               continue;
-                       }
-                       $target = $this->getRedirectTarget( $title );
-                       if ( !$target ) {
-                               continue;
-                       }
-                       $result[$target] = $key;
-               }
-               return $result;
-       }
-
-       /**
-        * @param int $key key to pull to the front
-        * @return array $array with the item at $key pulled to the front
-        */
-       private function pullFront( $key, $array ) {
-               $cut = array_splice( $array, $key, 1 );
-               array_unshift( $array, $cut[0] );
-               return $array;
-       }
-
-       /**
-        * Get a redirect's destination from a title
-        * @param Title $title A title to redirect. It may not redirect or even exist
-        * @return null|string If title exists and redirects, get the destination's prefixed name
-        */
-       private function getRedirectTarget( $title ) {
-               $page = WikiPage::factory( $title );
-               if ( !$page->exists() ) {
-                       return null;
-               }
-               $redir = $page->getRedirectTarget();
-               return $redir ? $redir->getPrefixedText() : null;
+               $rescorer = new SearchExactMatchRescorer();
+               return $rescorer->rescore( $search, $namespaces, $srchres, $limit );
        }
 
        /**
diff --git a/includes/search/SearchExactMatchRescorer.php b/includes/search/SearchExactMatchRescorer.php
new file mode 100644 (file)
index 0000000..0ff628d
--- /dev/null
@@ -0,0 +1,144 @@
+<?php
+/**
+ * Rescores results from a prefix search/opensearch to make sure the
+ * exact match is the first result.
+ *
+ * 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
+ */
+
+/**
+ * An utility class to rescore search results by looking for an exact match
+ * in the db and add the page found to the first position.
+ *
+ * NOTE: extracted from TitlePrefixSearch
+ * @ingroup Search
+ */
+class SearchExactMatchRescorer {
+       /**
+        * Default search backend does proper prefix searching, but custom backends
+        * may sort based on other algorithms that may cause the exact title match
+        * to not be in the results or be lower down the list.
+        * @param string $search the query
+        * @param int[] $namespaces the namespaces
+        * @param int $limit the max number of results to return
+        * @param string[] $srchres results
+        * @return string[] munged results
+        */
+       public function rescore( $search, $namespaces, $srchres, $limit ) {
+               // Pick namespace (based on PrefixSearch::defaultSearchBackend)
+               $ns = in_array( NS_MAIN, $namespaces ) ? NS_MAIN : $namespaces[0];
+               $t = Title::newFromText( $search, $ns );
+               if ( !$t || !$t->exists() ) {
+                       // No exact match so just return the search results
+                       return $srchres;
+               }
+               $string = $t->getPrefixedText();
+               $key = array_search( $string, $srchres );
+               if ( $key !== false ) {
+                       // Exact match was in the results so just move it to the front
+                       return $this->pullFront( $key, $srchres );
+               }
+               // Exact match not in the search results so check for some redirect handling cases
+               if ( $t->isRedirect() ) {
+                       $target = $this->getRedirectTarget( $t );
+                       $key = array_search( $target, $srchres );
+                       if ( $key !== false ) {
+                               // Exact match is a redirect to one of the returned matches so pull the
+                               // returned match to the front.  This might look odd but the alternative
+                               // is to put the redirect in front and drop the match.  The name of the
+                               // found match is often more descriptive/better formed than the name of
+                               // the redirect AND by definition they share a prefix.  Hopefully this
+                               // choice is less confusing and more helpful.  But it might not be.  But
+                               // it is the choice we're going with for now.
+                               return $this->pullFront( $key, $srchres );
+                       }
+                       $redirectTargetsToRedirect = $this->redirectTargetsToRedirect( $srchres );
+                       if ( isset( $redirectTargetsToRedirect[$target] ) ) {
+                               // The exact match and something in the results list are both redirects
+                               // to the same thing!  In this case we'll pull the returned match to the
+                               // top following the same logic above.  Again, it might not be a perfect
+                               // choice but it'll do.
+                               return $this->pullFront( $redirectTargetsToRedirect[$target], $srchres );
+                       }
+               } else {
+                       $redirectTargetsToRedirect = $this->redirectTargetsToRedirect( $srchres );
+                       if ( isset( $redirectTargetsToRedirect[$string] ) ) {
+                               // The exact match is the target of a redirect already in the results list so remove
+                               // the redirect from the results list and push the exact match to the front
+                               array_splice( $srchres, $redirectTargetsToRedirect[$string], 1 );
+                               array_unshift( $srchres, $string );
+                               return $srchres;
+                       }
+               }
+
+               // Exact match is totally unique from the other results so just add it to the front
+               array_unshift( $srchres, $string );
+               // And roll one off the end if the results are too long
+               if ( count( $srchres ) > $limit ) {
+                       array_pop( $srchres );
+               }
+               return $srchres;
+       }
+
+       /**
+        * @param string[] $titles as strings
+        * @return array redirect target prefixedText to index of title in titles
+        *   that is a redirect to it.
+        */
+       private function redirectTargetsToRedirect( $titles ) {
+               $result = array();
+               foreach ( $titles as $key => $titleText ) {
+                       $title = Title::newFromText( $titleText );
+                       if ( !$title || !$title->isRedirect() ) {
+                               continue;
+                       }
+                       $target = $this->getRedirectTarget( $title );
+                       if ( !$target ) {
+                               continue;
+                       }
+                       $result[$target] = $key;
+               }
+               return $result;
+       }
+
+       /**
+        * Returns an array where the element of $array at index $key becomes
+        * the first element.
+        * @param int $key key to pull to the front
+        * @return array $array with the item at $key pulled to the front
+        */
+       private function pullFront( $key, $array ) {
+               $cut = array_splice( $array, $key, 1 );
+               array_unshift( $array, $cut[0] );
+               return $array;
+       }
+
+       /**
+        * Get a redirect's destination from a title
+        * @param Title $title A title to redirect. It may not redirect or even exist
+        * @return null|string If title exists and redirects, get the destination's prefixed name
+        */
+       private function getRedirectTarget( $title ) {
+               $page = WikiPage::factory( $title );
+               if ( !$page->exists() ) {
+                       return null;
+               }
+               $redir = $page->getRedirectTarget();
+               return $redir ? $redir->getPrefixedText() : null;
+       }
+}
index 6859310..0119781 100644 (file)
@@ -228,7 +228,7 @@ class SpecialProtectedpages extends SpecialPage {
                }
 
                return '<span class="mw-input-with-label">' .
-                       Xml::label( $this->msg( 'restriction-type' )->text(), $this->IdType ) . '&#160;' .
+                       Xml::label( $this->msg( 'restriction-type' )->text(), $this->IdType ) . ' ' .
                        Xml::tags( 'select',
                                array( 'id' => $this->IdType, 'name' => $this->IdType ),
                                implode( "\n", $options ) ) . "</span>";
index a01df69..b5382a6 100644 (file)
@@ -403,8 +403,15 @@ class SpecialWatchlist extends ChangesListSpecialPage {
         */
        public function doHeader( $opts, $numRows ) {
                $user = $this->getUser();
+               $out = $this->getOutput();
 
-               $this->getOutput()->addSubtitle(
+               // if the user wishes, that the watchlist is reloaded, whenever a filter changes,
+               // add the module for that
+               if ( $user->getBoolOption( 'watchlistreloadautomatically' ) ) {
+                       $out->addModules( array( 'mediawiki.special.watchlist' ) );
+               }
+
+               $out->addSubtitle(
                        $this->msg( 'watchlistfor2', $user->getName() )
                                ->rawParams( SpecialEditWatchlist::buildTools( null ) )
                );
index 0092ada..a7f0923 100644 (file)
@@ -35,6 +35,7 @@
        "tog-watchlisthidebots": "Hide bot edits from the watchlist",
        "tog-watchlisthideminor": "Hide minor edits from the watchlist",
        "tog-watchlisthideliu": "Hide edits by logged in users from the watchlist",
+       "tog-watchlistreloadautomatically": "Reload the watchlist automatically whenever a filter is changed (JavaScript required)",
        "tog-watchlisthideanons": "Hide edits by anonymous users from the watchlist",
        "tog-watchlisthidepatrolled": "Hide patrolled edits from the watchlist",
        "tog-watchlisthidecategorization": "Hide categorization of pages",
index dcf26b3..2319663 100644 (file)
        "tog-watchlisthidebots": "[[Special:Preferences]], tab 'Watchlist'. Offers user to hide bot edits from watchlist. {{Gender}}\n\n{{Related|Preferences-watchlistrc-toggle}}",
        "tog-watchlisthideminor": "[[Special:Preferences]], tab 'Watchlist'. Offers user to hide minor edits from watchlist. {{Gender}}\n\n{{Related|Preferences-watchlistrc-toggle}}",
        "tog-watchlisthideliu": "Option in tab 'Watchlist' of [[Special:Preferences]]. {{Gender}}\n\n{{Related|Preferences-watchlistrc-toggle}}",
+       "tog-watchlistreloadautomatically": "[[Special:Preferences]], tab 'Watchlist'. Offers user to to automatically refresh the watchlist page, when a filter is changed.",
        "tog-watchlisthideanons": "Option in tab 'Watchlist' of [[Special:Preferences]]. {{Gender}}\n\n{{Related|Preferences-watchlistrc-toggle}}",
        "tog-watchlisthidepatrolled": "Option in Watchlist tab of [[Special:Preferences]]. {{Gender}}\n\n{{Related|Preferences-watchlistrc-toggle}}",
        "tog-watchlisthidecategorization": "Option in Watchlist tab of [[Special:Preferences]]. Offers user to hide/show categorization of pages. Appears next to checkboxes with labels such as {{msg-mw|tog-watchlisthideminor}}.",
index 0eb5118..aba5ce8 100644 (file)
@@ -1834,6 +1834,9 @@ return array(
                        'mediawiki.util',
                ),
        ),
+       'mediawiki.special.watchlist' => array(
+               'scripts' => 'resources/src/mediawiki.special/mediawiki.special.watchlist.js',
+       ),
        'mediawiki.special.javaScriptTest' => array(
                'scripts' => 'resources/src/mediawiki.special/mediawiki.special.javaScriptTest.js',
                'messages' => array_merge( Skin::getSkinNameMessages(), array(
diff --git a/resources/src/mediawiki.special/mediawiki.special.watchlist.js b/resources/src/mediawiki.special/mediawiki.special.watchlist.js
new file mode 100644 (file)
index 0000000..a35f4d1
--- /dev/null
@@ -0,0 +1,15 @@
+/*!
+ * JavaScript for Special:Watchlist
+ *
+ * This script is only loaded, if the user opt-in a setting in Special:Preferences,
+ * that the watchlist should be automatically reloaded, when a filter option is
+ * changed in the header form.
+ */
+jQuery( function ( $ ) {
+       // add a listener on all form elements in the header form
+       $( '#mw-watchlist-form input, #mw-watchlist-form select' ).on( 'change', function () {
+               // submit the form, when one of the input fields was changed
+               $( '#mw-watchlist-form' ).submit();
+       } );
+
+} );