Merge "Update padding of mw-ui-input to match OOUI"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 12 Sep 2017 14:27:53 +0000 (14:27 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 12 Sep 2017 14:27:53 +0000 (14:27 +0000)
19 files changed:
includes/DefaultSettings.php
includes/GlobalFunctions.php
includes/Linker.php
includes/Preferences.php
includes/Sanitizer.php
includes/api/ApiStashEdit.php
includes/parser/BlockLevelPass.php
includes/specialpage/ChangesListSpecialPage.php
includes/specials/SpecialVersion.php
includes/specials/SpecialWatchlist.php
languages/i18n/en.json
languages/i18n/qqq.json
resources/src/mediawiki.language/mediawiki.language.js
resources/src/mediawiki.legacy/shared.css
resources/src/mediawiki/mediawiki.util.js
tests/parser/parserTests.txt
tests/phpunit/includes/SanitizerTest.php
tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js
tests/qunit/suites/resources/mediawiki/mediawiki.util.test.js

index cf8e089..5b77d16 100644 (file)
@@ -3394,8 +3394,8 @@ $wgExperimentalHtmlIds = false;
 /**
  * How should section IDs be encoded?
  * This array can contain 1 or 2 elements, each of them can be one of:
- * - 'html5'  is modern HTML5 style encoding with minimal escaping. Allows to
- *            display Unicode characters in many browsers' address bars.
+ * - 'html5'  is modern HTML5 style encoding with minimal escaping. Displays Unicode
+ *            characters in most browsers' address bars.
  * - 'legacy' is old MediaWiki-style encoding, e.g. 啤酒 turns into .E5.95.A4.E9.85.92
  * - 'html5-legacy' corresponds to DEPRECATED $wgExperimentalHtmlIds mode. DO NOT use
  *            it for anything but migration off that mode (see below).
@@ -4920,6 +4920,7 @@ $wgDefaultUserOptions = [
        'previewontop' => 1,
        'rcdays' => 7,
        'rcenhancedfilters' => 0,
+       'rcenhancedfilters-disable' => 0,
        'rclimit' => 50,
        'rows' => 25, // @deprecated since 1.29 No longer used in core
        'showhiddencats' => 0,
@@ -6829,19 +6830,37 @@ $wgRCWatchCategoryMembership = false;
  */
 $wgUseRCPatrol = true;
 
+/**
+ * Whether a preference is displayed for structured change filters.
+ * If false, no preference is displayed and structured change filters are disabled.
+ * If true, structured change filters are *enabled* by default, and a preference is displayed
+ * that lets users disable them.
+ *
+ * Temporary variable during development and will be removed.
+ *
+ * @since 1.30
+ */
+$wgStructuredChangeFiltersShowPreference = false;
+
 /**
  * Whether to show the new experimental views (like namespaces, tags, and users) in
  * RecentChanges filters
+ *
+ * Temporary variable during development and will be removed.
  */
 $wgStructuredChangeFiltersEnableExperimentalViews = false;
 
 /**
  * Whether to allow users to use the experimental live update feature in the new RecentChanges UI
+ *
+ * Temporary variable during development and will be removed.
  */
 $wgStructuredChangeFiltersEnableLiveUpdate = false;
 
 /**
  * Whether to enable RCFilters app on Special:Watchlist
+ *
+ * Temporary variable during development and will be removed.
  */
 $wgStructuredChangeFiltersOnWatchlist = false;
 
index fa97515..e80ecf1 100644 (file)
@@ -3160,6 +3160,7 @@ function wfShorthandToInteger( $string = '', $default = -1 ) {
 /**
  * Get the normalised IETF language tag
  * See unit test for examples.
+ * See mediawiki.language.bcp47 for the JavaScript implementation.
  *
  * @param string $code The language code.
  * @return string The language code which complying with BCP 47 standards.
index aedb704..dccd99c 100644 (file)
@@ -1539,10 +1539,16 @@ class Linker {
                if ( $sectionIndex !== false ) {
                        $classes .= " tocsection-$sectionIndex";
                }
-               return "\n<li class=\"$classes\"><a href=\"#" .
-                       $anchor . '"><span class="tocnumber">' .
-                       $tocnumber . '</span> <span class="toctext">' .
-                       $tocline . '</span></a>';
+
+               // \n<li class="$classes"><a href="#$anchor"><span class="tocnumber">
+               // $tocnumber</span> <span class="toctext">$tocline</span></a>
+               return "\n" . Html::openElement( 'li', [ 'class' => $classes ] )
+                       . Html::rawElement( 'a',
+                               [ 'href' => "#$anchor" ],
+                               Html::element( 'span', [ 'class' => 'tocnumber' ], $tocnumber )
+                                       . ' '
+                                       . Html::rawElement( 'span', [ 'class' => 'toctext' ], $tocline )
+                       );
        }
 
        /**
index c64e8a8..c29c4b9 100644 (file)
@@ -958,6 +958,15 @@ class Preferences {
                                'label-message' => 'tog-shownumberswatching',
                        ];
                }
+
+               if ( $config->get( 'StructuredChangeFiltersShowPreference' ) ) {
+                       $defaultPreferences['rcenhancedfilters-disable'] = [
+                               'type' => 'toggle',
+                               'section' => 'rc/advancedrc',
+                               'label-message' => 'rcfilters-preference-label',
+                               'help-message' => 'rcfilters-preference-help',
+                       ];
+               }
        }
 
        /**
index ed09701..7d17cd1 100644 (file)
@@ -1284,7 +1284,6 @@ class Sanitizer {
                $mode = $wgFragmentMode[self::ID_PRIMARY];
 
                $id = self::escapeIdInternal( $id, $mode );
-               $id = self::urlEscapeId( $id, $mode );
 
                return $id;
        }
@@ -1302,23 +1301,6 @@ class Sanitizer {
                global $wgExternalInterwikiFragmentMode;
 
                $id = self::escapeIdInternal( $id, $wgExternalInterwikiFragmentMode );
-               $id = self::urlEscapeId( $id, $wgExternalInterwikiFragmentMode );
-
-               return $id;
-       }
-
-       /**
-        * Helper for escapeIdFor*() functions. URL-escapes the ID if needed.
-        *
-        * @param string $id String to escape
-        * @param string $mode One of modes from $wgFragmentMode
-        * @return string
-        */
-       private static function urlEscapeId( $id, $mode ) {
-               if ( $mode === 'html5' ) {
-                       $id = urlencode( $id );
-                       $id = str_replace( '%3A', ':', $id );
-               }
 
                return $id;
        }
index d03fca8..8a9de06 100644 (file)
@@ -74,6 +74,9 @@ class ApiStashEdit extends ApiBase {
                if ( strlen( $params['stashedtexthash'] ) ) {
                        // Load from cache since the client indicates the text is the same as last stash
                        $textHash = $params['stashedtexthash'];
+                       if ( !preg_match( '/^[0-9a-f]{40}$/', $textHash ) ) {
+                               $this->dieWithError( 'apierror-stashedit-missingtext', 'missingtext' );
+                       }
                        $textKey = $cache->makeKey( 'stashedit', 'text', $textHash );
                        $text = $cache->get( $textKey );
                        if ( !is_string( $text ) ) {
index 599fbf6..fab9ab7 100644 (file)
@@ -257,12 +257,17 @@ class BlockLevelPass {
                                        $output .= $this->nextItem( $prefix[$commonPrefixLength - 1] );
                                }
 
+                               # Close an open <dt> if we have a <dd> (":") starting on this line
+                               if ( $this->DTopen && $commonPrefixLength > 0 && $prefix[$commonPrefixLength - 1] === ':' ) {
+                                       $output .= $this->nextItem( ':' );
+                               }
+
                                # Open prefixes where appropriate.
                                if ( $lastPrefix && $prefixLength > $commonPrefixLength ) {
                                        $output .= "\n";
                                }
                                while ( $prefixLength > $commonPrefixLength ) {
-                                       $char = substr( $prefix, $commonPrefixLength, 1 );
+                                       $char = $prefix[$commonPrefixLength];
                                        $output .= $this->openList( $char );
 
                                        if ( ';' === $char ) {
index 7f6fdf7..0762bf7 100644 (file)
@@ -1557,7 +1557,29 @@ abstract class ChangesListSpecialPage extends SpecialPage {
         * @return bool
         */
        public function isStructuredFilterUiEnabled() {
-               return $this->getUser()->getOption( 'rcenhancedfilters' );
+               if ( $this->getRequest()->getBool( 'rcfilters' ) ) {
+                       return true;
+               }
+
+               if ( $this->getConfig()->get( 'StructuredChangeFiltersShowPreference' ) ) {
+                       return !$this->getUser()->getOption( 'rcenhancedfilters-disable' );
+               } else {
+                       return $this->getUser()->getOption( 'rcenhancedfilters' );
+               }
+       }
+
+       /**
+        * Check whether the structured filter UI is enabled by default (regardless of
+        * this particular user's setting)
+        *
+        * @return bool
+        */
+       public function isStructuredFilterUiEnabledByDefault() {
+               if ( $this->getConfig()->get( 'StructuredChangeFiltersShowPreference' ) ) {
+                       return !$this->getUser()->getDefaultOption( 'rcenhancedfilters-disable' );
+               } else {
+                       return $this->getUser()->getDefaultOption( 'rcenhancedfilters' );
+               }
        }
 
        abstract function getDefaultLimit();
index 3ea1d03..f176b40 100644 (file)
@@ -203,6 +203,7 @@ class SpecialVersion extends SpecialPage {
                        'Roan Kattouw', 'Trevor Parscal', 'Bryan Tong Minh', 'Sam Reed',
                        'Victor Vasiliev', 'Rotem Liss', 'Platonides', 'Antoine Musso',
                        'Timo Tijhof', 'Daniel Kinzler', 'Jeroen De Dauw', 'Brad Jorsch',
+                       'Bartosz Dziewoński', 'Ed Sanders', 'Moriel Schottlender',
                        $othersLink, $translatorsLink
                ];
 
index 7049744..ec64869 100644 (file)
@@ -112,9 +112,15 @@ class SpecialWatchlist extends ChangesListSpecialPage {
        }
 
        public function isStructuredFilterUiEnabled() {
-               return parent::isStructuredFilterUiEnabled()
-                       && ( $this->getConfig()->get( 'StructuredChangeFiltersOnWatchlist' )
-                               || $this->getRequest()->getBool( 'rcfilters' ) );
+               return $this->getRequest()->getBool( 'rcfilters' ) || (
+                       $this->getConfig()->get( 'StructuredChangeFiltersOnWatchlist' ) &&
+                       $this->getUser()->getOption( 'rcenhancedfilters' )
+               );
+       }
+
+       public function isStructuredFilterUiEnabledByDefault() {
+               return $this->getConfig()->get( 'StructuredChangeFiltersOnWatchlist' ) &&
+                       $this->getUser()->getDefaultOption( 'rcenhancedfilters' );
        }
 
        /**
index 8add481..5dd8345 100644 (file)
        "rcfilters-watchlist-markseen-button": "Mark all changes as seen",
        "rcfilters-watchlist-edit-watchlist-button": "Edit your list of watched pages",
        "rcfilters-watchlist-showupdated": "Changes to pages you haven't visited since the changes occurred are in <strong>bold</strong>, with solid markers.",
+       "rcfilters-preference-label": "Hide the improved version of Recent Changes",
+       "rcfilters-preference-help": "Rolls back the 2017 interface redesign and all tools added then and since.",
        "rcnotefrom": "Below {{PLURAL:$5|is the change|are the changes}} since <strong>$3, $4</strong> (up to <strong>$1</strong> shown).",
        "rclistfromreset": "Reset date selection",
        "rclistfrom": "Show new changes starting from $2, $3",
index 56cbbbd..9d2e77d 100644 (file)
        "rcfilters-watchlist-markseen-button": "Label for the button to mark all changes as seen on [[Special:Watchlist]] when using the structured filters interface.",
        "rcfilters-watchlist-edit-watchlist-button": "Label for the button to edit the watched pages on [[Special:Watchlist]] when using the structured filters interface.\n\nCf. {{msg-mw|watchlisttools-edit}}.",
        "rcfilters-watchlist-showupdated": "Message at the top of [[Special:Watchlist]] when the Structured filters are enabled that describes what unseen changes look like.\n\nCf. {{msg-mw|wlheader-showupdated}}",
+       "rcfilters-preference-label": "Option in RecentChanges tab of [[Special:Preferences]].",
+       "rcfilters-preference-help": "Explanation for the option in the RecentChanges tab of [[Special:Preferences]].",
        "rcnotefrom": "This message is displayed at [[Special:RecentChanges]] when viewing recentchanges from some specific time.\n\nThe corresponding message is {{msg-mw|Rclistfrom}}.\n\nParameters:\n* $1 - the maximum number of changes that are displayed\n* $2 - (Optional) a date and time\n* $3 - a date\n* $4 - a time\n* $5 - Number of changes are displayed, for use with PLURAL",
        "rclistfromreset": "Used on [[Special:RecentChanges]] to reset a selection of a certain date range.",
        "rclistfrom": "Used on [[Special:RecentChanges]]. Parameters:\n* $1 - (Currently not use) date and time. The date and the time adds to the rclistfrom description.\n* $2 - time. The time adds to the rclistfrom link description (with split of date and time).\n* $3 - date. The date adds to the rclistfrom link description (with split of date and time).\n\nThe corresponding message is {{msg-mw|Rcnotefrom}}.",
index 3726a68..6a52434 100644 (file)
 
                setSpecialCharacters: function ( data ) {
                        this.specialCharacters = data;
+               },
+
+               /**
+                * Formats language tags according the BCP47 standard.
+                * See wfBCP47 for the PHP implementation.
+                *
+                * @param {string} languageTag Well-formed language tag
+                * @return {string}
+                */
+               bcp47: function ( languageTag ) {
+                       var formatted,
+                               isFirstSegment = true,
+                               isPrivate = false,
+                               segments = languageTag.split( '-' );
+
+                       formatted = segments.map( function ( segment ) {
+                               var newSegment;
+
+                               // when previous segment is x, it is a private segment and should be lc
+                               if ( isPrivate ) {
+                                       newSegment = segment.toLowerCase();
+                               // ISO 3166 country code
+                               } else if ( segment.length === 2 && !isFirstSegment ) {
+                                       newSegment = segment.toUpperCase();
+                               // ISO 15924 script code
+                               } else if ( segment.length === 4 && !isFirstSegment ) {
+                                       newSegment = segment.charAt( 0 ).toUpperCase() + segment.substring( 1 ).toLowerCase();
+                               // Use lowercase for other cases
+                               } else {
+                                       newSegment = segment.toLowerCase();
+                               }
+
+                               isPrivate = segment.toLowerCase() === 'x';
+                               isFirstSegment = false;
+
+                               return newSegment;
+                       } );
+
+                       return formatted.join( '-' );
                }
        } );
 
index c6ad655..9930525 100644 (file)
@@ -278,7 +278,7 @@ p.mw-delete-editreasons {
 
 /* The auto-generated edit comments */
 .autocomment {
-       color: #808080;
+       color: #72777d;
 }
 
 /** Generic minor/bot/newpage styling (recent changes) */
@@ -361,11 +361,11 @@ a.mw-selflink:visited {
  * keep in sync with commonPrint.css
  */
 table.wikitable {
-       margin: 1em 0;
        background-color: #f8f9fa;
+       color: #222;
+       margin: 1em 0;
        border: 1px solid #a2a9b1;
        border-collapse: collapse;
-       color: #000;
 }
 
 table.wikitable > tr > th,
index 34f7eba..fb34a89 100644 (file)
                 * @return {string} Encoded string
                 */
                escapeIdForLink: function ( str ) {
-                       var mode = mw.config.get( 'wgFragmentMode' )[ 0 ],
-                               id = escapeIdInternal( str, mode );
-
-                       if ( mode === 'html5' ) {
-                               id = encodeURIComponent( id ).replace( /%3A/g, ':' );
-                       }
+                       var mode = mw.config.get( 'wgFragmentMode' )[ 0 ];
 
-                       return id;
+                       return escapeIdInternal( str, mode );
                },
 
                /**
index 3f93793..00d2538 100644 (file)
@@ -4332,6 +4332,21 @@ Definition Lists: Mixed Lists: Test 10
 
 !! end
 
+# This is a regression test for T175099
+# html/php+tidy is insufficient since Tidy covers up the bug.
+# But once Tidy is replaced with RemexHTML, html/php+tidy is good enough
+!! test
+Definition Lists: Mixed Lists: Test 11
+!! wikitext
+;a
+:*b
+!! html
+<dl><dt>a</dt>
+<dd>
+<ul><li>b</li></ul></dd></dl>
+
+!! end
+
 # The Parsoid team disagrees with the PHP parser's seemingly-random
 # rules regarding dd/dt on the next two tests.  Parsoid is more
 # consistent, and recognizes the shared nesting and keeps the
@@ -4339,7 +4354,7 @@ Definition Lists: Mixed Lists: Test 10
 # (And tidy again converts <dt> to <dd> before 'bar'.)
 
 !! test
-Definition Lists: Mixed Lists: Test 11
+Definition Lists: Mixed Lists: Test 12
 !! wikitext
 *#*#;*;;foo :bar
 *#*#;boo :baz
@@ -29307,10 +29322,10 @@ wgFragmentMode=[ 'html5', 'legacy' ]
 <ul>
 <li class="toclevel-1 tocsection-1"><a href="#Foo_bar"><span class="tocnumber">1</span> <span class="toctext">Foo bar</span></a></li>
 <li class="toclevel-1 tocsection-2"><a href="#foo_Bar_2"><span class="tocnumber">2</span> <span class="toctext">foo Bar</span></a></li>
-<li class="toclevel-1 tocsection-3"><a href="#%D0%A2%D0%B5%D1%81%D1%82"><span class="tocnumber">3</span> <span class="toctext">Тест</span></a></li>
-<li class="toclevel-1 tocsection-4"><a href="#%D0%A2%D0%B5%D1%81%D1%82_2"><span class="tocnumber">4</span> <span class="toctext">Тест</span></a></li>
-<li class="toclevel-1 tocsection-5"><a href="#%D1%82%D0%B5%D1%81%D1%82"><span class="tocnumber">5</span> <span class="toctext">тест</span></a></li>
-<li class="toclevel-1 tocsection-6"><a href="#Hey_%3C_%23_%22_%3E_%25_:_%27"><span class="tocnumber">6</span> <span class="toctext">Hey &lt; # " &gt;&#160;%&#160;: '</span></a></li>
+<li class="toclevel-1 tocsection-3"><a href="#Тест"><span class="tocnumber">3</span> <span class="toctext">Тест</span></a></li>
+<li class="toclevel-1 tocsection-4"><a href="#Тест_2"><span class="tocnumber">4</span> <span class="toctext">Тест</span></a></li>
+<li class="toclevel-1 tocsection-5"><a href="#тест"><span class="tocnumber">5</span> <span class="toctext">тест</span></a></li>
+<li class="toclevel-1 tocsection-6"><a href="#Hey_&lt;_#_&quot;_&gt;_%_:_'"><span class="tocnumber">6</span> <span class="toctext">Hey &lt; # " &gt;&#160;%&#160;: '</span></a></li>
 </ul>
 </div>
 
@@ -29320,9 +29335,9 @@ wgFragmentMode=[ 'html5', 'legacy' ]
 <h2><span id=".D0.A2.D0.B5.D1.81.D1.82_2"></span><span class="mw-headline" id="Тест_2">Тест</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=4" title="Edit section: Тест">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
 <h2><span id=".D1.82.D0.B5.D1.81.D1.82"></span><span class="mw-headline" id="тест">тест</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=5" title="Edit section: тест">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
 <h2><span id="Hey_.3C_.23_.22_.3E_.25_:_.27"></span><span class="mw-headline" id="Hey_&lt;_#_&quot;_&gt;_%_:_'">Hey &lt; # " &gt;&#160;%&#160;: '</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=6" title="Edit section: Hey &lt; # &quot; &gt; % : '">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
-<p><a href="#Foo_bar">#Foo bar</a> <a href="#foo_Bar">#foo Bar</a> <a href="#%D0%A2%D0%B5%D1%81%D1%82">#Тест</a> <a href="#%D1%82%D0%B5%D1%81%D1%82">#тест</a> <a href="#Hey_%3C_%23_%22_%3E_%25_:_%27">#Hey &lt; # " &gt;&#160;%&#160;: '</a>
-</p><p>%F0%9F%92%A9 <span id="%F0%9F%92%A9"></span>
-</p><p><a href="#%E5%95%A4%E9%85%92">#啤酒</a> <a href="#%E5%95%A4%E9%85%92">#啤酒</a>
+<p><a href="#Foo_bar">#Foo bar</a> <a href="#foo_Bar">#foo Bar</a> <a href="#Тест">#Тест</a> <a href="#тест">#тест</a> <a href="#Hey_&lt;_#_&quot;_&gt;_%_:_'">#Hey &lt; # " &gt;&#160;%&#160;: '</a>
+</p><p>💩 <span id="💩"></span>
+</p><p><a href="#啤酒">#啤酒</a> <a href="#啤酒">#啤酒</a>
 </p>
 !! end
 
@@ -29401,10 +29416,10 @@ wgFragmentMode=[ 'html5' ]
 <ul>
 <li class="toclevel-1 tocsection-1"><a href="#Foo_bar"><span class="tocnumber">1</span> <span class="toctext">Foo bar</span></a></li>
 <li class="toclevel-1 tocsection-2"><a href="#foo_Bar_2"><span class="tocnumber">2</span> <span class="toctext">foo Bar</span></a></li>
-<li class="toclevel-1 tocsection-3"><a href="#%D0%A2%D0%B5%D1%81%D1%82"><span class="tocnumber">3</span> <span class="toctext">Тест</span></a></li>
-<li class="toclevel-1 tocsection-4"><a href="#%D0%A2%D0%B5%D1%81%D1%82_2"><span class="tocnumber">4</span> <span class="toctext">Тест</span></a></li>
-<li class="toclevel-1 tocsection-5"><a href="#%D1%82%D0%B5%D1%81%D1%82"><span class="tocnumber">5</span> <span class="toctext">тест</span></a></li>
-<li class="toclevel-1 tocsection-6"><a href="#Hey_%3C_%23_%22_%3E_%25_:_%27"><span class="tocnumber">6</span> <span class="toctext">Hey &lt; # " &gt;&#160;%&#160;: '</span></a></li>
+<li class="toclevel-1 tocsection-3"><a href="#Тест"><span class="tocnumber">3</span> <span class="toctext">Тест</span></a></li>
+<li class="toclevel-1 tocsection-4"><a href="#Тест_2"><span class="tocnumber">4</span> <span class="toctext">Тест</span></a></li>
+<li class="toclevel-1 tocsection-5"><a href="#тест"><span class="tocnumber">5</span> <span class="toctext">тест</span></a></li>
+<li class="toclevel-1 tocsection-6"><a href="#Hey_&lt;_#_&quot;_&gt;_%_:_'"><span class="tocnumber">6</span> <span class="toctext">Hey &lt; # " &gt;&#160;%&#160;: '</span></a></li>
 </ul>
 </div>
 
@@ -29414,8 +29429,8 @@ wgFragmentMode=[ 'html5' ]
 <h2><span class="mw-headline" id="Тест_2">Тест</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=4" title="Edit section: Тест">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
 <h2><span class="mw-headline" id="тест">тест</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=5" title="Edit section: тест">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
 <h2><span class="mw-headline" id="Hey_&lt;_#_&quot;_&gt;_%_:_'">Hey &lt; # " &gt;&#160;%&#160;: '</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&amp;action=edit&amp;section=6" title="Edit section: Hey &lt; # &quot; &gt; % : '">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
-<p><a href="#Foo_bar">#Foo bar</a> <a href="#foo_Bar">#foo Bar</a> <a href="#%D0%A2%D0%B5%D1%81%D1%82">#Тест</a> <a href="#%D1%82%D0%B5%D1%81%D1%82">#тест</a> <a href="#Hey_%3C_%23_%22_%3E_%25_:_%27">#Hey &lt; # " &gt;&#160;%&#160;: '</a>
-</p><p>%F0%9F%92%A9 <span id="%F0%9F%92%A9"></span>
-</p><p><a href="#%E5%95%A4%E9%85%92">#啤酒</a> <a href="#%E5%95%A4%E9%85%92">#啤酒</a>
+<p><a href="#Foo_bar">#Foo bar</a> <a href="#foo_Bar">#foo Bar</a> <a href="#Тест">#Тест</a> <a href="#тест">#тест</a> <a href="#Hey_&lt;_#_&quot;_&gt;_%_:_'">#Hey &lt; # " &gt;&#160;%&#160;: '</a>
+</p><p>💩 <span id="💩"></span>
+</p><p><a href="#啤酒">#啤酒</a> <a href="#啤酒">#啤酒</a>
 </p>
 !! end
index d506623..7472fb9 100644 (file)
@@ -456,7 +456,6 @@ class SanitizerTest extends MediaWikiTestCase {
                $text = 'foo тест_#%!\'()[]:<>';
                $legacyEncoded = 'foo_.D1.82.D0.B5.D1.81.D1.82_.23.25.21.27.28.29.5B.5D:.3C.3E';
                $html5Encoded = 'foo_тест_#%!\'()[]:<>';
-               $html5Escaped = 'foo_%D1%82%D0%B5%D1%81%D1%82_%23%25%21%27%28%29%5B%5D:%3C%3E';
                $html5Experimental = 'foo_тест_!_()[]:<>';
 
                // Settings: last element is $wgExternalInterwikiFragmentMode, the rest is $wgFragmentMode
@@ -484,20 +483,20 @@ class SanitizerTest extends MediaWikiTestCase {
                        // New world: HTML5 links, legacy fallbacks
                        [ 'Attribute', $newLegacy, $text, $html5Encoded, Sanitizer::ID_PRIMARY ],
                        [ 'Attribute', $newLegacy, $text, $legacyEncoded, Sanitizer::ID_FALLBACK ],
-                       [ 'Link', $newLegacy, $text, $html5Escaped ],
+                       [ 'Link', $newLegacy, $text, $html5Encoded ],
                        [ 'ExternalInterwiki', $newLegacy, $text, $legacyEncoded ],
 
                        // Distant future: no legacy fallbacks, but still linking to leagacy wikis
                        [ 'Attribute', $new, $text, $html5Encoded, Sanitizer::ID_PRIMARY ],
                        [ 'Attribute', $new, $text, false, Sanitizer::ID_FALLBACK ],
-                       [ 'Link', $new, $text, $html5Escaped ],
+                       [ 'Link', $new, $text, $html5Encoded ],
                        [ 'ExternalInterwiki', $new, $text, $legacyEncoded ],
 
                        // Just before the heat death of universe: external interwikis are also HTML5 \m/
                        [ 'Attribute', $allNew, $text, $html5Encoded, Sanitizer::ID_PRIMARY ],
                        [ 'Attribute', $allNew, $text, false, Sanitizer::ID_FALLBACK ],
-                       [ 'Link', $allNew, $text, $html5Escaped ],
-                       [ 'ExternalInterwiki', $allNew, $text, $html5Escaped ],
+                       [ 'Link', $allNew, $text, $html5Encoded ],
+                       [ 'ExternalInterwiki', $allNew, $text, $html5Encoded ],
 
                        // Someone flipped $wgExperimentalHtmlIds on
                        [ 'Attribute', $experimentalLegacy, $text, $html5Experimental, Sanitizer::ID_PRIMARY ],
@@ -508,7 +507,7 @@ class SanitizerTest extends MediaWikiTestCase {
                        // Migration from $wgExperimentalHtmlIds to modern HTML5
                        [ 'Attribute', $newExperimental, $text, $html5Encoded, Sanitizer::ID_PRIMARY ],
                        [ 'Attribute', $newExperimental, $text, $html5Experimental, Sanitizer::ID_FALLBACK ],
-                       [ 'Link', $newExperimental, $text, $html5Escaped ],
+                       [ 'Link', $newExperimental, $text, $html5Encoded ],
                        [ 'ExternalInterwiki', $newExperimental, $text, $legacyEncoded ],
                ];
        }
index b965079..5ce61ea 100644 (file)
@@ -1,7 +1,7 @@
 ( function ( mw, $ ) {
        'use strict';
 
-       var grammarTests;
+       var grammarTests, bcp47Tests;
 
        QUnit.module( 'mediawiki.language', QUnit.newMwEnvironment( {
                setup: function () {
                assert.equal( mw.language.listToText( [ 'a', 'b' ] ), 'a and b', 'Two items' );
                assert.equal( mw.language.listToText( [ 'a', 'b', 'c' ] ), 'a, b and c', 'More than two items' );
        } );
+
+       bcp47Tests = [
+               // Extracted from BCP 47 (list not exhaustive)
+               // # 2.1.1
+               [ 'en-ca-x-ca', 'en-CA-x-ca' ],
+               [ 'sgn-be-fr', 'sgn-BE-FR' ],
+               [ 'az-latn-x-latn', 'az-Latn-x-latn' ],
+               // # 2.2
+               [ 'sr-Latn-RS', 'sr-Latn-RS' ],
+               [ 'az-arab-ir', 'az-Arab-IR' ],
+
+               // # 2.2.5
+               [ 'sl-nedis', 'sl-nedis' ],
+               [ 'de-ch-1996', 'de-CH-1996' ],
+
+               // # 2.2.6
+               [
+                       'en-latn-gb-boont-r-extended-sequence-x-private',
+                       'en-Latn-GB-boont-r-extended-sequence-x-private'
+               ],
+
+               // Examples from BCP 47 Appendix A
+               // # Simple language subtag:
+               [ 'DE', 'de' ],
+               [ 'fR', 'fr' ],
+               [ 'ja', 'ja' ],
+
+               // # Language subtag plus script subtag:
+               [ 'zh-hans', 'zh-Hans' ],
+               [ 'sr-cyrl', 'sr-Cyrl' ],
+               [ 'sr-latn', 'sr-Latn' ],
+
+               // # Extended language subtags and their primary language subtag
+               // # counterparts:
+               [ 'zh-cmn-hans-cn', 'zh-cmn-Hans-CN' ],
+               [ 'cmn-hans-cn', 'cmn-Hans-CN' ],
+               [ 'zh-yue-hk', 'zh-yue-HK' ],
+               [ 'yue-hk', 'yue-HK' ],
+
+               // # Language-Script-Region:
+               [ 'zh-hans-cn', 'zh-Hans-CN' ],
+               [ 'sr-latn-RS', 'sr-Latn-RS' ],
+
+               // # Language-Variant:
+               [ 'sl-rozaj', 'sl-rozaj' ],
+               [ 'sl-rozaj-biske', 'sl-rozaj-biske' ],
+               [ 'sl-nedis', 'sl-nedis' ],
+
+               // # Language-Region-Variant:
+               [ 'de-ch-1901', 'de-CH-1901' ],
+               [ 'sl-it-nedis', 'sl-IT-nedis' ],
+
+               // # Language-Script-Region-Variant:
+               [ 'hy-latn-it-arevela', 'hy-Latn-IT-arevela' ],
+
+               // # Language-Region:
+               [ 'de-de', 'de-DE' ],
+               [ 'en-us', 'en-US' ],
+               [ 'es-419', 'es-419' ],
+
+               // # Private use subtags:
+               [ 'de-ch-x-phonebk', 'de-CH-x-phonebk' ],
+               [ 'az-arab-x-aze-derbend', 'az-Arab-x-aze-derbend' ],
+               /**
+                * Previous test does not reflect the BCP 47 which states:
+                *  az-Arab-x-AZE-derbend
+                * AZE being private, it should be lower case, hence the test above
+                * should probably be:
+                * [ 'az-arab-x-aze-derbend', 'az-Arab-x-AZE-derbend' ],
+                */
+
+               // # Private use registry values:
+               [ 'x-whatever', 'x-whatever' ],
+               [ 'qaa-qaaa-qm-x-southern', 'qaa-Qaaa-QM-x-southern' ],
+               [ 'de-qaaa', 'de-Qaaa' ],
+               [ 'sr-latn-qm', 'sr-Latn-QM' ],
+               [ 'sr-qaaa-rs', 'sr-Qaaa-RS' ],
+
+               // # Tags that use extensions
+               [ 'en-us-u-islamcal', 'en-US-u-islamcal' ],
+               [ 'zh-cn-a-myext-x-private', 'zh-CN-a-myext-x-private' ],
+               [ 'en-a-myext-b-another', 'en-a-myext-b-another' ]
+
+               // # Invalid:
+               // de-419-DE
+               // a-DE
+               // ar-a-aaa-b-bbb-a-ccc
+       ];
+
+       QUnit.test( 'mw.language.bcp47', function ( assert ) {
+               bcp47Tests.forEach( function ( data ) {
+                       var input = data[ 0 ],
+                               expected = data[ 1 ];
+                       assert.equal( mw.language.bcp47( input ), expected );
+               } );
+       } );
 }( mediaWiki, jQuery ) );
index 2efe9cd..bb27626 100644 (file)
                // Test cases are kept in sync with SanitizerTest.php
                var text = 'foo тест_#%!\'()[]:<>',
                        legacyEncoded = 'foo_.D1.82.D0.B5.D1.81.D1.82_.23.25.21.27.28.29.5B.5D:.3C.3E',
-                       html5Escaped = 'foo_%D1%82%D0%B5%D1%81%D1%82_%23%25!\'()%5B%5D:%3C%3E',
+                       html5Encoded = 'foo_тест_#%!\'()[]:<>',
                        html5Experimental = 'foo_тест_!_()[]:<>',
                        // Settings: this is wgFragmentMode
                        legacy = [ 'legacy' ],
                        // Transition to a new world: legacy links with HTML5 fallback
                        [ legacyNew, text, legacyEncoded ],
                        // New world: HTML5 links, legacy fallbacks
-                       [ newLegacy, text, html5Escaped ],
+                       [ newLegacy, text, html5Encoded ],
                        // Distant future: no legacy fallbacks
-                       [ allNew, text, html5Escaped ],
+                       [ allNew, text, html5Encoded ],
                        // Someone flipped wgExperimentalHtmlIds on
                        [ experimentalLegacy, text, html5Experimental ],
                        // Migration from wgExperimentalHtmlIds to modern HTML5
-                       [ newExperimental, text, html5Escaped ]
+                       [ newExperimental, text, html5Encoded ]
                ], function ( index, testCase ) {
                        mw.config.set( 'wgFragmentMode', testCase[ 0 ] );