/*! * VisualEditor DataModel MWInternalLinkAnnotation class. * * @copyright See AUTHORS.txt * @license The MIT License (MIT); see LICENSE.txt */ /** * DataModel MediaWiki internal link annotation. * * Example HTML sources: * * * * @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', 'mw:MediaLink' ]; // mw:MediaLink to non-existent files come with typeof="mw:Error" ve.dm.MWInternalLinkAnnotation.static.allowedRdfaTypes = [ 'mw:Error', 'mw:LocalizedAttrs' ]; ve.dm.MWInternalLinkAnnotation.static.toDataElement = function ( domElements, converter ) { const resource = domElements[ 0 ].getAttribute( 'resource' ); let targetData; if ( resource ) { targetData = mw.libs.ve.parseParsoidResourceName( resource ); } else { targetData = mw.libs.ve.getTargetDataFromHref( domElements[ 0 ].getAttribute( 'href' ), converter.getTargetHtmlDocument() ); if ( !targetData.isInternal ) { return ve.dm.MWExternalLinkAnnotation.static.toDataElement( domElements, converter ); } } return { type: this.name, attributes: { title: targetData.title, normalizedTitle: this.normalizeTitle( targetData.title ), lookupTitle: this.getLookupTitle( targetData.title ) } }; }; /** * Build element from a given mw.Title and raw title * * @param {mw.Title} title The title to link to. * @return {Object} The element. */ ve.dm.MWInternalLinkAnnotation.static.dataElementFromTitle = function ( title ) { let target = title.toText(); if ( title.getFragment() ) { target += '#' + title.getFragment(); } const element = { type: this.name, attributes: { title: target, normalizedTitle: this.normalizeTitle( title ), lookupTitle: this.getLookupTitle( title ) } }; return element; }; /** * Build a ve.dm.MWInternalLinkAnnotation from a given mw.Title. * * @param {mw.Title} title The title to link to. * @return {ve.dm.MWInternalLinkAnnotation} The annotation. */ ve.dm.MWInternalLinkAnnotation.static.newFromTitle = function ( title ) { const element = this.dataElementFromTitle( title ); return new ve.dm.MWInternalLinkAnnotation( element ); }; ve.dm.MWInternalLinkAnnotation.static.toDomElements = function () { const parentResult = ve.dm.LinkAnnotation.static.toDomElements.apply( this, arguments ); // we just created that link so the 'rel' attribute should be safe parentResult[ 0 ].setAttribute( 'rel', 'mw:WikiLink' ); return parentResult; }; ve.dm.MWInternalLinkAnnotation.static.getHref = function ( dataElement ) { let title = dataElement.attributes.title; if ( title.slice( 0, 1 ) === '#' ) { // Special case: For a newly created link to a #fragment with // no explicit title use the current title as prefix (T218581) // TODO: Pass a 'doc' param to getPageName title = ve.init.target.getPageName() + title; } return mw.libs.ve.encodeParsoidResourceName( title ); }; /** * 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 ) { const 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 ) { const 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 ) { const 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.htmlMsg( 'visualeditor-changedesc-link-href', this.wrapText( 'del', change.from ), this.wrapText( 'ins', 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 );