mediawiki-extensions-Visual.../modules/ve-mw/dm/annotations/ve.dm.MWInternalLinkAnnotation.js
James D. Forrester 0a7a845a42 doc: Bump copyright year
Change-Id: I0b299c840ede1a1b8552cecfc70c5760ab036181
2018-01-03 17:45:07 +00:00

265 lines
8.2 KiB
JavaScript

/*!
* VisualEditor DataModel MWInternalLinkAnnotation class.
*
* @copyright 2011-2018 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* DataModel MediaWiki internal link annotation.
*
* Example HTML sources:
*
* <a rel="mw:WikiLink">
*
* @class
* @extends ve.dm.LinkAnnotation
* @constructor
* @param {Object} element
*/
ve.dm.MWInternalLinkAnnotation = function VeDmMWInternalLinkAnnotation() {
// Parent constructor
ve.dm.MWInternalLinkAnnotation.super.apply( this, arguments );
};
/* Inheritance */
OO.inheritClass( ve.dm.MWInternalLinkAnnotation, ve.dm.LinkAnnotation );
/* Static Properties */
ve.dm.MWInternalLinkAnnotation.static.name = 'link/mwInternal';
ve.dm.MWInternalLinkAnnotation.static.matchRdfaTypes = [ 'mw:WikiLink' ];
ve.dm.MWInternalLinkAnnotation.static.toDataElement = function ( domElements, converter ) {
var targetData = this.getTargetDataFromHref(
domElements[ 0 ].getAttribute( 'href' ),
converter.getTargetHtmlDocument()
);
return {
type: this.name,
attributes: {
hrefPrefix: targetData.hrefPrefix,
title: targetData.title,
normalizedTitle: this.normalizeTitle( targetData.title ),
lookupTitle: this.getLookupTitle( targetData.title ),
origTitle: targetData.rawTitle
}
};
};
/**
* Build a ve.dm.MWInternalLinkAnnotation from a given mw.Title.
*
* @param {mw.Title} title The title to link to.
* @param {string} [rawTitle] String from which the title was created
* @return {ve.dm.MWInternalLinkAnnotation} The annotation.
*/
ve.dm.MWInternalLinkAnnotation.static.newFromTitle = function ( title, rawTitle ) {
var element,
target = title.toText(),
namespaceIds = mw.config.get( 'wgNamespaceIds' );
if ( title.getNamespaceId() === namespaceIds.file || title.getNamespaceId() === namespaceIds.category ) {
// File: or Category: link
// We have to prepend a colon so this is interpreted as a link
// rather than an image inclusion or categorization
target = ':' + target;
}
if ( title.getFragment() ) {
target += '#' + title.getFragment();
}
element = {
type: 'link/mwInternal',
attributes: {
title: target,
normalizedTitle: ve.dm.MWInternalLinkAnnotation.static.normalizeTitle( title ),
lookupTitle: ve.dm.MWInternalLinkAnnotation.static.getLookupTitle( title )
}
};
if ( rawTitle ) {
element.attributes.origTitle = rawTitle;
}
return new ve.dm.MWInternalLinkAnnotation( element );
};
/**
* Parse URL to get title it points to.
*
* @param {string} href
* @param {HTMLDocument|string} doc Document whose base URL to use, or base URL as a string.
* @return {Object} Information about the given href
* @return {string} return.title
* The title of the internal link, else the original href if href is external
* @return {string} return.rawTitle
* The title without URL decoding and underscore normalization applied
* @return {string} return.hrefPrefix
* Any ./ or ../ prefixes on a relative link
* @return {boolean} return.isInternal
* True if the href pointed to the local wiki, false if href is external
*/
ve.dm.MWInternalLinkAnnotation.static.getTargetDataFromHref = function ( href, doc ) {
var relativeBase, relativeBaseRegex, relativeHref, isInternal, matches;
function regexEscape( str ) {
return str.replace( /([.?*+^$[\]\\(){}|-])/g, '\\$1' );
}
// Protocol relative base
relativeBase = ve.resolveUrl( mw.config.get( 'wgArticlePath' ), doc ).replace( /^https?:/i, '' );
relativeBaseRegex = new RegExp( regexEscape( relativeBase ).replace( regexEscape( '$1' ), '(.*)' ) );
// Protocol relative href
relativeHref = href.replace( /^https?:/i, '' );
// Paths without a host portion are assumed to be internal
isInternal = !/^\/\//.test( relativeHref );
// Check if this matches the server's article path
matches = relativeHref.match( relativeBaseRegex );
if ( matches && matches[ 1 ].indexOf( '?' ) === -1 ) {
// Take the relative path
href = matches[ 1 ];
isInternal = true;
}
// The href is simply the title, unless we're dealing with a page that has slashes in its name
// in which case it's preceded by one or more instances of "./" or "../", so strip those
matches = href.match( /^((?:\.\.?\/)*)(.*)$/ );
// Percent-encoded characters are forbidden in titles... but if we're
// copy/pasting URLs around, they're likely to wind up encoded at this
// point. So decode them, otherwise this is going to cause failures
// elsewhere.
return {
title: ve.decodeURIComponentIntoArticleTitle( matches[ 2 ] ),
rawTitle: matches[ 2 ],
hrefPrefix: matches[ 1 ],
isInternal: isInternal
};
};
ve.dm.MWInternalLinkAnnotation.static.toDomElements = function () {
var parentResult = ve.dm.LinkAnnotation.static.toDomElements.apply( this, arguments );
parentResult[ 0 ].setAttribute( 'rel', 'mw:WikiLink' );
return parentResult;
};
ve.dm.MWInternalLinkAnnotation.static.getHref = function ( dataElement ) {
var href,
title = dataElement.attributes.title,
origTitle = dataElement.attributes.origTitle;
if ( origTitle !== undefined && ve.decodeURIComponentIntoArticleTitle( origTitle ) === title ) {
// Restore href from origTitle
href = origTitle;
// Only use hrefPrefix if restoring from origTitle
if ( dataElement.attributes.hrefPrefix ) {
href = dataElement.attributes.hrefPrefix + href;
}
} else {
// Don't escape slashes in the title; they represent subpages.
href = title.split( /(\/|#)/ ).map( function ( part ) {
if ( part === '/' || part === '#' ) {
return part;
} else {
return encodeURIComponent( part );
}
} ).join( '' );
}
return href;
};
/**
* Normalize title for comparison purposes.
* E.g. capitalisation and underscores.
*
* @param {string|mw.Title} original Original title
* @return {string} Normalized title, or the original string if it is invalid
*/
ve.dm.MWInternalLinkAnnotation.static.normalizeTitle = function ( original ) {
var title = original instanceof mw.Title ? original : mw.Title.newFromText( original );
if ( !title ) {
return original;
}
return title.getPrefixedText() + ( title.getFragment() !== null ? '#' + title.getFragment() : '' );
};
/**
* Normalize title for lookup (search suggestion, existence) purposes.
*
* @param {string|mw.Title} original Original title
* @return {string} Normalized title, or the original string if it is invalid
*/
ve.dm.MWInternalLinkAnnotation.static.getLookupTitle = function ( original ) {
var title = original instanceof mw.Title ? original : mw.Title.newFromText( original );
if ( !title ) {
return original;
}
return title.getPrefixedText();
};
/**
* Get the fragment for a title
*
* @static
* @param {string|mw.Title} original Original title
* @return {string|null} Fragment for the title, or null if it was invalid or missing
*/
ve.dm.MWInternalLinkAnnotation.static.getFragment = function ( original ) {
var title = original instanceof mw.Title ? original : mw.Title.newFromText( original );
if ( !title ) {
return null;
}
return title.getFragment();
};
ve.dm.MWInternalLinkAnnotation.static.describeChange = function ( key, change ) {
if ( key === 'title' ) {
return ve.msg( 'visualeditor-changedesc-link-href', change.from, change.to );
}
return null;
};
/* Methods */
/**
* @inheritdoc
*/
ve.dm.MWInternalLinkAnnotation.prototype.getComparableObject = function () {
return {
type: this.getType(),
normalizedTitle: this.getAttribute( 'normalizedTitle' )
};
};
/**
* @inheritdoc
*/
ve.dm.MWInternalLinkAnnotation.prototype.getComparableHtmlAttributes = function () {
// Assume that wikitext never adds meaningful html attributes for comparison purposes,
// although ideally this should be decided by Parsoid (Bug T95028).
return {};
};
/**
* @inheritdoc
*/
ve.dm.MWInternalLinkAnnotation.prototype.getDisplayTitle = function () {
return this.getAttribute( 'normalizedTitle' );
};
/**
* Convenience wrapper for .getFragment() on the current element.
*
* @see #static-getFragment
* @return {string} Fragment for the title, or an empty string if it was invalid
*/
ve.dm.MWInternalLinkAnnotation.prototype.getFragment = function () {
return this.constructor.static.getFragment( this.getAttribute( 'normalizedTitle' ) );
};
/* Registration */
ve.dm.modelRegistry.register( ve.dm.MWInternalLinkAnnotation );