regularLiteral, regularLiteralWithoutBar, regularLiteralWithoutSpace, backslash, anyCharacter,
escapedOrLiteralWithoutSpace, escapedOrLiteralWithoutBar, escapedOrRegularLiteral,
whitespace, dollar, digits,
- openExtlink, closeExtlink, openLink, closeLink, templateName, pipe, colon,
+ openExtlink, closeExtlink, wikilinkPage, wikilinkContents, openLink, closeLink, templateName, pipe, colon,
templateContents, openTemplate, closeTemplate,
nonWhitespaceExpression, paramExpression, expression, result;
var result = nOrMore( 1, escapedOrLiteralWithoutBar )();
return result === null ? null : result.join('');
}
+
+ // Used for wikilink page names. Like literalWithoutBar, but
+ // without allowing escapes.
+ function unescapedLiteralWithoutBar() {
+ var result = nOrMore( 1, regularLiteralWithoutBar )();
+ return result === null ? null : result.join('');
+ }
+
function literal() {
var result = nOrMore( 1, escapedOrRegularLiteral )();
return result === null ? null : result.join('');
}
openLink = makeStringParser( '[[' );
closeLink = makeStringParser( ']]' );
+ pipe = makeStringParser( '|' );
+
+ function template() {
+ var result = sequence( [
+ openTemplate,
+ templateContents,
+ closeTemplate
+ ] );
+ return result === null ? null : result[1];
+ }
+
+ wikilinkPage = choice( [
+ unescapedLiteralWithoutBar,
+ template
+ ] );
+
+ function pipedWikilink() {
+ var result = sequence( [
+ wikilinkPage,
+ pipe,
+ expression
+ ] );
+ return result === null ? null : [ result[0], result[2] ];
+ }
+
+ wikilinkContents = choice( [
+ pipedWikilink,
+ wikilinkPage // unpiped link
+ ] );
+
function link() {
- var result, parsedResult;
+ var result, parsedResult, parsedLinkContents;
result = null;
+
parsedResult = sequence( [
openLink,
- expression,
+ wikilinkContents,
closeLink
] );
if ( parsedResult !== null ) {
- result = [ 'WLINK', parsedResult[1] ];
+ parsedLinkContents = parsedResult[1];
+ result = [ 'WLINK' ].concat( parsedLinkContents );
}
return result;
}
// use a CONCAT operator if there are multiple nodes, otherwise return the first node, raw.
return expr.length > 1 ? [ 'CONCAT' ].concat( expr ) : expr[0];
}
- pipe = makeStringParser( '|' );
+
function templateWithReplacement() {
var result = sequence( [
templateName,
] );
openTemplate = makeStringParser('{{');
closeTemplate = makeStringParser('}}');
- function template() {
- var result = sequence( [
- openTemplate,
- templateContents,
- closeTemplate
- ] );
- return result === null ? null : result[1];
- }
nonWhitespaceExpression = choice( [
template,
link,
replacement,
literalWithoutBar
] );
+
expression = choice( [
template,
link,
replacement,
literal
] );
+
function start() {
var result = nOrMore( 0, expression )();
if ( result === null ) {
/**
* Transform wiki-link
- * TODO unimplemented
+ *
+ * TODO:
+ * It only handles basic cases, either no pipe, or a pipe with an explicit
+ * anchor.
+ *
+ * It does not attempt to handle features like the pipe trick.
+ * However, the pipe trick should usually not be present in wikitext retrieved
+ * from the server, since the replacement is done at save time.
+ * It may, though, if the wikitext appears in extension-controlled content.
+ *
* @param nodes
*/
- wlink: function () {
- return 'unimplemented';
+ wlink: function ( nodes ) {
+ var page, anchor, url;
+
+ page = nodes[0];
+ url = mw.util.wikiGetlink( page );
+
+ // [[Some Page]] or [[Namespace:Some Page]]
+ if ( nodes.length === 1 ) {
+ anchor = page;
+ }
+
+ /*
+ * [[Some Page|anchor text]] or
+ * [[Namespace:Some Page|anchor]
+ */
+ else {
+ anchor = nodes[1];
+ }
+
+ return $( '<a />' ).attr( {
+ title: page,
+ href: url
+ } ).text( anchor );
},
/**
var form = nodes[0],
word = nodes[1];
return word && form && this.language.convertGrammar( word, form );
+ },
+
+ /**
+ * Tranform parsed structure into a int: (interface language) message include
+ * Invoked by putting {{MediaWiki:othermessage}} into a message
+ * @param {Array} of nodes
+ * @return {string} Other message
+ */
+ int: function ( nodes ) {
+ return mw.jqueryMsg.getMessageFunction()( nodes[0].toLowerCase() );
}
};
// Deprecated! don't rely on gM existing.
// Caching is somewhat problematic, because we do need different message functions for different maps, so
// we'd have to cache the parser as a member of this.map, which sounds a bit ugly.
// Do not use mw.jqueryMsg unless required
- if ( this.map.get( this.key ).indexOf( '{{' ) < 0 ) {
+ if ( !/\{\{|\[/.test(this.map.get( this.key ) ) ) {
// Fall back to mw.msg's simple parser
return oldParser.apply( this );
}
( function ( mw, $ ) {
+var mwLanguageCache = {}, oldGetOuterHtml;
+
QUnit.module( 'mediawiki.jqueryMsg', QUnit.newMwEnvironment( {
setup: function () {
this.orgMwLangauge = mw.language;
mw.language = $.extend( true, {}, this.orgMwLangauge );
+ oldGetOuterHtml = $.fn.getOuterHtml;
+ $.fn.getOuterHtml = function () {
+ var $div = $( '<div>' ), html;
+ $div.append( $( this ).eq( 0 ).clone() );
+ html = $div.html();
+ $div.empty();
+ $div = undefined;
+ return html;
+ };
},
teardown: function () {
mw.language = this.orgMwLangauge;
+ $.fn.getOuterHtml = oldGetOuterHtml;
}
}) );
-var mwLanguageCache = {};
function getMwLanguage( langCode, cb ) {
if ( mwLanguageCache[langCode] !== undefined ) {
mwLanguageCache[langCode].add( cb );
} );
});
+QUnit.test( 'Links', 6, function ( assert ) {
+ var parser = mw.jqueryMsg.getMessageFunction(),
+ expectedListUsers,
+ expectedDisambiguationsText,
+ expectedMultipleBars,
+ expectedSpecialCharacters,
+ specialCharactersPageName;
+
+ /*
+ The below three are all identical to or based on real messages. For disambiguations-text,
+ the bold was removed because it is not yet implemented.
+ */
+
+ mw.messages.set( 'statistics-users', '注册[[Special:ListUsers|用户]]' );
+
+ expectedListUsers = '注册' + $( '<a>' ).attr( {
+ title: 'Special:ListUsers',
+ href: mw.util.wikiGetlink( 'Special:ListUsers' )
+ } ).text( '用户' ).getOuterHtml();
+
+ assert.equal(
+ parser( 'statistics-users' ),
+ expectedListUsers,
+ 'Piped wikilink'
+ );
+
+ expectedDisambiguationsText = 'The following pages contain at least one link to a disambiguation page.\nThey may have to link to a more appropriate page instead.<br>\nA page is treated as a disambiguation page if it uses a template that is linked from ' +
+ $( '<a>' ).attr( {
+ title: 'MediaWiki:Disambiguationspage',
+ href: mw.util.wikiGetlink( 'MediaWiki:Disambiguationspage' )
+ } ).text( 'MediaWiki:Disambiguationspage' ).getOuterHtml() + '.';
+ mw.messages.set( 'disambiguations-text', 'The following pages contain at least one link to a disambiguation page.\nThey may have to link to a more appropriate page instead.<br />\nA page is treated as a disambiguation page if it uses a template that is linked from [[MediaWiki:Disambiguationspage]].' );
+ assert.equal(
+ parser( 'disambiguations-text' ),
+ expectedDisambiguationsText,
+ 'Wikilink without pipe'
+ );
+
+ mw.messages.set( 'version-entrypoints-index-php', '[https://www.mediawiki.org/wiki/Manual:index.php index.php]' );
+ assert.equal(
+ parser( 'version-entrypoints-index-php' ),
+ '<a href="https://www.mediawiki.org/wiki/Manual:index.php">index.php</a>',
+ 'External link'
+ );
+
+ // Pipe trick is not supported currently, but should not parse as text either.
+ mw.messages.set( 'pipe-trick', '[[Tampa, Florida|]]' );
+ assert.equal(
+ parser( 'pipe-trick' ),
+ 'Error: Parse error at position 0 in input: [[Tampa, Florida|]]',
+ 'Pipe trick should return error string.'
+ );
+
+ expectedMultipleBars = $( '<a>' ).attr( {
+ title: 'Main Page',
+ href: mw.util.wikiGetlink( 'Main Page' )
+ } ).text( 'Main|Page' ).getOuterHtml();
+ mw.messages.set( 'multiple-bars', '[[Main Page|Main|Page]]' );
+ assert.equal(
+ parser( 'multiple-bars' ),
+ expectedMultipleBars,
+ 'Bar in anchor'
+ );
+
+ specialCharactersPageName = '"Who" wants to be a millionaire & live on \'Exotic Island\'?';
+ expectedSpecialCharacters = $( '<a>' ).attr( {
+ title: specialCharactersPageName,
+ href: mw.util.wikiGetlink( specialCharactersPageName )
+ } ).text( specialCharactersPageName ).getOuterHtml();
+
+ mw.messages.set( 'special-characters', '[[' + specialCharactersPageName + ']]' );
+ assert.equal(
+ parser( 'special-characters' ),
+ expectedSpecialCharacters,
+ 'Special characters'
+ );
+});
+
+QUnit.test( 'Int magic word', 4, function ( assert ) {
+ var parser = mw.jqueryMsg.getMessageFunction(),
+ newarticletextSource = 'You have followed a link to a page that does not exist yet. To create the page, start typing in the box below (see the [[{{Int:Helppage}}|help page]] for more info). If you are here by mistake, click your browser\'s back button.',
+ expectedNewarticletext;
+
+ mw.messages.set( 'helppage', 'Help:Contents' );
+
+ expectedNewarticletext = 'You have followed a link to a page that does not exist yet. To create the page, start typing in the box below (see the ' +
+ $( '<a>' ).attr( {
+ title: mw.msg( 'helppage' ),
+ href: mw.util.wikiGetlink( mw.msg( 'helppage' ) )
+ } ).text( 'help page' ).getOuterHtml() + ' for more info). If you are here by mistake, click your browser\'s back button.';
+
+ mw.messages.set( 'newarticletext', newarticletextSource );
+
+ assert.equal(
+ parser( 'newarticletext' ),
+ expectedNewarticletext,
+ 'Link with nested message'
+ );
+
+ mw.messages.set( 'portal-url', 'Project:Community portal' );
+ mw.messages.set( 'see-portal-url', '{{Int:portal-url}} is an important community page.' );
+ assert.equal(
+ parser( 'see-portal-url' ),
+ 'Project:Community portal is an important community page.',
+ 'Nested message'
+ );
+
+ mw.messages.set( 'newarticletext-lowercase',
+ newarticletextSource.replace( 'Int:Helppage', 'int:helppage' ) );
+
+ assert.equal(
+ parser( 'newarticletext-lowercase' ),
+ expectedNewarticletext,
+ 'Link with nested message, lowercase include'
+ );
+
+ mw.messages.set( 'uses-missing-int', '{{int:doesnt-exist}}' );
+
+ assert.equal(
+ parser( 'uses-missing-int' ),
+ '[doesnt-exist]',
+ 'int: where nested message does not exist'
+ );
+});
+
+// Tests that getMessageFunction is used for messages with curly braces or square brackets,
+// but not otherwise.
+QUnit.test( 'Calls to mw.msg', 8, function ( assert ) {
+ // Should be
+ var map, oldGMF, outerCalled, innerCalled;
+
+ map = new mw.Map();
+ map.set( {
+ 'curly-brace': '{{int:message}}',
+ 'single-square-bracket': '[https://www.mediawiki.org/ MediaWiki]',
+ 'double-square-bracket': '[[Some page]]',
+ 'regular': 'Other message'
+ } );
+
+ oldGMF = mw.jqueryMsg.getMessageFunction;
+
+ mw.jqueryMsg.getMessageFunction = function() {
+ outerCalled = true;
+ return function() {
+ innerCalled = true;
+ };
+ };
+
+ function verifyGetMessageFunction( key, shouldCall ) {
+ outerCalled = false;
+ innerCalled = false;
+ ( new mw.Message( map, key ) ).parser();
+ assert.strictEqual( outerCalled, shouldCall, 'Outer function called for ' + key );
+ assert.strictEqual( innerCalled, shouldCall, 'Inner function called for ' + key );
+ }
+
+ verifyGetMessageFunction( 'curly-brace', true );
+ verifyGetMessageFunction( 'single-square-bracket', true );
+ verifyGetMessageFunction( 'double-square-bracket', true );
+ verifyGetMessageFunction( 'regular', false );
+
+ mw.jqueryMsg.getMessageFunction = oldGMF;
+} );
+
}( mediaWiki, jQuery ) );