/**
* 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).
'previewontop' => 1,
'rcdays' => 7,
'rcenhancedfilters' => 0,
+ 'rcenhancedfilters-disable' => 0,
'rclimit' => 50,
'rows' => 25, // @deprecated since 1.29 No longer used in core
'showhiddencats' => 0,
*/
$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;
/**
* 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.
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 )
+ );
}
/**
'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',
+ ];
+ }
}
/**
$mode = $wgFragmentMode[self::ID_PRIMARY];
$id = self::escapeIdInternal( $id, $mode );
- $id = self::urlEscapeId( $id, $mode );
return $id;
}
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;
}
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 ) ) {
$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 ) {
* @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();
'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
];
}
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' );
}
/**
"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",
"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}}.",
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( '-' );
}
} );
/* The auto-generated edit comments */
.autocomment {
- color: #808080;
+ color: #72777d;
}
/** Generic minor/bot/newpage styling (recent changes) */
* 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,
* @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 );
},
/**
!! 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
# (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
<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 < # " > % : '</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_<_#_"_>_%_:_'"><span class="tocnumber">6</span> <span class="toctext">Hey < # " > % : '</span></a></li>
</ul>
</div>
<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&action=edit&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&action=edit&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_<_#_"_>_%_:_'">Hey < # " > % : '</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=6" title="Edit section: Hey < # " > % : '">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 < # " > % : '</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_<_#_"_>_%_:_'">#Hey < # " > % : '</a>
+</p><p>💩 <span id="💩"></span>
+</p><p><a href="#啤酒">#啤酒</a> <a href="#啤酒">#啤酒</a>
</p>
!! end
<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 < # " > % : '</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_<_#_"_>_%_:_'"><span class="tocnumber">6</span> <span class="toctext">Hey < # " > % : '</span></a></li>
</ul>
</div>
<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&action=edit&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&action=edit&section=5" title="Edit section: тест">edit</a><span class="mw-editsection-bracket">]</span></span></h2>
<h2><span class="mw-headline" id="Hey_<_#_"_>_%_:_'">Hey < # " > % : '</span><span class="mw-editsection"><span class="mw-editsection-bracket">[</span><a href="/index.php?title=Parser_test&action=edit&section=6" title="Edit section: Hey < # " > % : '">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 < # " > % : '</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_<_#_"_>_%_:_'">#Hey < # " > % : '</a>
+</p><p>💩 <span id="💩"></span>
+</p><p><a href="#啤酒">#啤酒</a> <a href="#啤酒">#啤酒</a>
</p>
!! end
$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
// 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 ],
// 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 ],
];
}
( 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 ) );
// 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 ] );