/** * Simple link handler. Registers after template expansions, as an * asynchronous transform. * * @author Gabriel Wicke * * TODO: keep round-trip information in meta tag or the like */ var jshashes = require('jshashes'), PegTokenizer = require('./mediawiki.tokenizer.peg.js').PegTokenizer; function WikiLinkHandler( manager, isInclude ) { this.manager = manager; this.manager.addTransform( this.onWikiLink.bind( this ), this.rank, 'tag', 'wikilink' ); // create a new peg parser for image options.. if ( !this.imageParser ) { // Actually the regular tokenizer, but we'll call it with the // img_options production only. WikiLinkHandler.prototype.imageParser = new PegTokenizer(); } } WikiLinkHandler.prototype.rank = 1.15; // after AttributeExpander WikiLinkHandler.prototype.onWikiLink = function ( token, frame, cb ) { var env = this.manager.env, href = token.attribs[0].v; var title = this.manager.env.makeTitleFromPrefixedText( env.tokensToString( href ) ); if ( title.ns.isFile() ) { cb( this.renderFile( token, frame, cb, title ) ); } else if ( title.ns.isCategory() ) { // TODO: implement cb( { } ); } else { // Check if page exists // //console.warn( 'title: ' + JSON.stringify( title ) ); var normalizedHref = title.makeLink(), obj = new TagTk( 'a', [ new KV( 'href', normalizedHref ), new KV('rel', 'mw:wikiLink') ] , token.dataAttribs ), content = token.attribs.slice(2); if ( href !== normalizedHref ) { obj.dataAttribs.sHref = env.tokensToString( href ); } //console.warn('content: ' + JSON.stringify( content, null, 2 ) ); // XXX: handle trail if ( content.length ) { var out = []; for ( var i = 0, l = content.length; i < l ; i++ ) { out = out.concat( content[i].v ); if ( i < l - 1 ) { out.push( '|' ); } } content = out; } else { content = [ env.decodeURI( env.tokensToString( href ) ) ]; obj.dataAttribs.gc = 1; } var tail = token.attribs[1].v; if ( tail ) { obj.dataAttribs.tail = tail; content.push( tail ); } cb ( { tokens: [obj].concat( content, [ new EndTagTk( 'a' ) ] ) } ); } }; WikiLinkHandler.prototype._simpleImageOptions = { // halign 'left': 'halign', 'right': 'halign', 'center': 'halign', 'float': 'halign', 'none': 'halign', // valign 'baseline': 'valign', 'sub': 'valign', 'super': 'valign', 'top': 'valign', 'text-top': 'valign', 'middle': 'valign', 'bottom': 'valign', 'text-bottom': 'valign', // format 'border': 'format', 'frameless': 'format', 'frame': 'format', 'thumbnail': 'format', 'thumb': 'format' }; WikiLinkHandler.prototype._prefixImageOptions = { 'link': 'link', 'alt': 'alt', 'page': 'page', 'thumbnail': 'thumb', 'thumb': 'thumb', 'upright': 'aspect' }; WikiLinkHandler.prototype.renderFile = function ( token, frame, cb, title ) { var env = this.manager.env; // distinguish media types // if image: parse options // Slice off the target and tail var content = token.attribs.slice(2); var MD5 = new jshashes.MD5(), hash = MD5.hex( title.key ), // TODO: Hackhack.. Move to proper test harness setup! path = [ this.manager.env.wgUploadPath, hash[0], hash.substr(0, 2), title.key ].join('/'); // extract options var options = [], oHash = {}, caption = []; for( var i = 0, l = content.length; i 0 ) { width = width * oHash.aspect; } else { width *= 0.75; } } var figurestyle = "width: " + (width + 5) + "px;", figureclass = "thumb tright thumbinner"; // set horizontal alignment if ( oHash.halign ) { if ( oHash.halign === 'left' ) { figurestyle += ' float: left;'; figureclass = "thumb tleft thumbinner"; } else if ( oHash.halign === 'center' ) { figureclass = "thumb center thumbinner"; } else if ( oHash.halign === 'none' ) { figureclass = "thumb thumbinner"; } else { figurestyle += ' float: right;'; } } else { figurestyle += ' float: right;'; } // XXX: set vertical alignment (valign) // XXX: support other formats (border, frameless, frame) // XXX: support prefixes var thumb = [ new TagTk( 'figure', [ new KV('class', figureclass), new KV('style', figurestyle), new KV('typeof', 'http://mediawiki.org/rdf/Thumb'), // XXX: define this globally? new KV('prefix', "mw: http://mediawiki.org/rdf/terms/") ] ), new TagTk( 'a', [ new KV('href', title.makeLink()), new KV('class', 'image') ] ), new SelfclosingTagTk( 'img', [ new KV('src', path), new KV('width', width + 'px'), //new KV('height', '160px'), new KV('class', 'thumbimage'), new KV('alt', oHash.alt || title.key ), // Add resource as CURIE- needs global default prefix // definition. new KV('resource', '[:' + title.getPrefixedText() + ']') ] ), new EndTagTk( 'a' ), new SelfclosingTagTk ( 'a', [ new KV('href', title.makeLink()), new KV('class', 'internal sprite details magnify'), new KV('title', 'View photo details') ] ), new TagTk( 'figcaption', [ new KV('class', 'thumbcaption'), new KV('property', 'mw:thumbcaption') ] ) ].concat( caption, [ new EndTagTk( 'figcaption' ), new EndTagTk( 'figure' ) ] ); // set round-trip information on the wrapping figure token thumb[0].dataAttribs = token.dataAttribs; /* * Wikia DOM:
A DeLorean DMC-12 from the front with the gull-wing doors open
test
Continuation of the caption
ChristianAdded by Christian
*/ //console.warn( 'thumbtokens: ' + JSON.stringify( thumb, null, 2 ) ); return { tokens: thumb }; }; function ExternalLinkHandler( manager, isInclude ) { this.manager = manager; this.manager.addTransform( this.onUrlLink.bind( this ), this.rank, 'tag', 'urllink' ); this.manager.addTransform( this.onExtLink.bind( this ), this.rank - 0.001, 'tag', 'extlink' ); this.manager.addTransform( this.onEnd.bind( this ), this.rank, 'end' ); // create a new peg parser for image options.. if ( !this.imageParser ) { // Actually the regular tokenizer, but we'll call it with the // img_options production only. ExternalLinkHandler.prototype.imageParser = new PegTokenizer(); } this._reset(); } ExternalLinkHandler.prototype._reset = function () { this.linkCount = 1; }; ExternalLinkHandler.prototype.rank = 1.15; ExternalLinkHandler.prototype._imageExtensions = { 'jpg': true, 'png': true, 'gif': true, 'svg': true }; ExternalLinkHandler.prototype._isImageLink = function ( href ) { var bits = href.split( '.' ); return bits.length > 1 && this._imageExtensions[ bits[bits.length - 1] ] && href.match( /^https?:\/\// ); }; ExternalLinkHandler.prototype.onUrlLink = function ( token, frame, cb ) { var env = this.manager.env, href = env.sanitizeURI( env.tokensToString( env.lookupKV( token.attribs, 'href' ).v ) ); if ( this._isImageLink( href ) ) { cb( { tokens: [ new SelfclosingTagTk( 'img', [ new KV( 'src', href ), new KV( 'alt', href.split('/').last() ), new KV('rel', 'mw:externalImage') ], { stx: 'urllink' } ) ] } ); } else { cb( { tokens: [ new TagTk( 'a', [ new KV( 'href', href ), new KV('rel', 'mw:extLink') ], { stx: 'urllink' } ), href, new EndTagTk( 'a' ) ] } ); } }; // Bracketed external link ExternalLinkHandler.prototype.onExtLink = function ( token, manager, cb ) { var env = this.manager.env, href = env.tokensToString( env.lookupKV( token.attribs, 'href' ).v ), content= env.lookupKV( token.attribs, 'content' ).v; href = env.sanitizeURI( href ); //console.warn('extlink href: ' + href ); //console.warn( 'content: ' + JSON.stringify( content, null, 2 ) ); // validate the href if ( ! content.length ) { content = ['[' + this.linkCount + ']']; this.linkCount++; } if ( this.imageParser.tokenizeURL( href ) ) { if ( content.length === 1 && content[0].constructor === String && this.imageParser.tokenizeURL( content[0] ) && this._isImageLink( content[0] ) ) { var src = content[0]; content = [ new SelfclosingTagTk( 'img', [ new KV( 'src', src ), new KV( 'alt', src.split('/').last() ) ], { type: 'extlink' }) ]; } cb( { tokens: [ new TagTk ( 'a', [ new KV('href', href), new KV('rel', 'mw:extLink') ], token.dataAttribs ) ].concat( content, [ new EndTagTk( 'a' )]) } ); } else { cb( { tokens: ['[', href, ' ' ].concat( content, [']'] ) } ); } }; ExternalLinkHandler.prototype.onEnd = function ( token, manager, cb ) { this._reset(); cb( { tokens: [ token ] } ); }; if (typeof module == "object") { module.exports.WikiLinkHandler = WikiLinkHandler; module.exports.ExternalLinkHandler = ExternalLinkHandler; }