mediawiki-extensions-Visual.../modules/ve-mw/ui/actions/ve.ui.MWLinkAction.js
Bartosz Dziewoński 5afd15b2cc ve.ui.MWLinkAction: Use delayed sequence
The trailing space at the end of the regexp is no longer required to
prevent matching (and executing) too soon. We also don't need to know
about trailing punctuation.

However, to prevent the match from ending in the middle of typing, we
have to allow for ' ' and '-' at the end of the match.

Tweaked tests to better match the intent now that a trailing space is
not included, but they pass without changes too, the command is quite
permissive.

Bug: T117165
Depends-On: Ie36046fa43ce49f8a25c99f2de577eb296d68a51
Depends-On: I2af0a738afa43295bf6d7d612cac4349bc6cd20d
Change-Id: I7c28d5c93b1a441387ad05a75846af83d2b21b6a
2017-03-15 21:58:13 +00:00

174 lines
5.2 KiB
JavaScript

/*!
* VisualEditor UserInterface MWLinkAction class.
*
* @copyright 2011-2017 VisualEditor Team and others; see http://ve.mit-license.org
*/
/**
* Link action.
*
* Opens either MWLinkAnnotationInspector or MWLinkNodeInspector depending on what is selected.
*
* @class
* @extends ve.ui.LinkAction
* @constructor
* @param {ve.ui.Surface} surface Surface to act on
*/
ve.ui.MWLinkAction = function VeUiMWLinkAction( surface ) {
// Parent constructor
ve.ui.MWLinkAction.super.call( this, surface );
};
/* Inheritance */
OO.inheritClass( ve.ui.MWLinkAction, ve.ui.LinkAction );
/* Static Properties */
/**
* List of allowed methods for the action.
*
* @static
* @property
*/
ve.ui.MWLinkAction.static.methods = ve.ui.MWLinkAction.super.static.methods.concat( [ 'open', 'autolinkMagicLink' ] );
/* Static methods */
/**
* Get a link annotation from specified link text
*
* This is a static version of the method that can be used in the converter.
*
* @static
* @param {string} linktext Link text
* @param {HTMLDocument} doc Document
* @return {ve.dm.MWExternalLinkAnnotation|ve.dm.MWInternalLinkAnnotation} The annotation to use
*/
ve.ui.MWLinkAction.static.getLinkAnnotation = function ( linktext, doc ) {
var title, targetData,
href = linktext;
// Is this a "magic link"?
if ( ve.dm.MWMagicLinkNode.static.validateContent( linktext ) ) {
return ve.dm.MWMagicLinkNode.static.annotationFromContent( linktext );
}
// Is this an internal link?
targetData = ve.dm.MWInternalLinkAnnotation.static.getTargetDataFromHref( href, doc );
if ( targetData.isInternal ) {
title = mw.Title.newFromText( targetData.title );
return ve.dm.MWInternalLinkAnnotation.static.newFromTitle( title );
}
// It's an external link.
return new ve.dm.MWExternalLinkAnnotation( {
type: 'link/mwExternal',
attributes: { href: href }
} );
};
/* Methods */
/**
* Match the trailing punctuation set used for autolinks in wikitext.
* Closing parens are only stripped if open parens are missing from the
* candidate text, so that URLs with embedded matched parentheses (like
* wiki articles with disambiguation text) autolink nicely.
*
* @method
* @inheritdoc
*/
ve.ui.MWLinkAction.prototype.getTrailingPunctuation = function ( candidate ) {
// This is:
// * the "trailing punctuation" character set from
// Parse.php::makeFreeExternalLink(): [,;.:!?] and sometimes [)]
// * extended with characters banned by EXT_LINK_URL_CLASS: []<>"
// * further extended with international close quotes: "'”’›»“‘‹«」』
// https://en.wikipedia.org/wiki/Quotation_mark
return /\(/.test( candidate ) ?
/[,;.:!?\[\]<>\"\'”’›»“‘‹«」』]+$/ :
/[,;.:!?\[\]<>\"\'”’›»“‘‹«」』)]+$/;
};
/**
* @method
* @inheritdoc
* @return {ve.dm.MWExternalLinkAnnotation|ve.dm.MWInternalLinkAnnotation} The annotation to use
*/
ve.ui.MWLinkAction.prototype.getLinkAnnotation = function ( linktext ) {
return this.constructor.static.getLinkAnnotation( linktext, this.surface.getModel().getDocument().getHtmlDocument() );
};
/**
* Autolink the selected RFC/PMID/ISBN, which may have trailing punctuation
* followed by whitespace.
*
* @see ve.ui.LinkAction#autolinkUrl
* @method
* @return {boolean}
* True if the selection is a valid RFC/PMID/ISBN and the autolink action
* was executed; otherwise false.
*/
ve.ui.MWLinkAction.prototype.autolinkMagicLink = function () {
return this.autolink( function ( linktext ) {
return ve.dm.MWMagicLinkNode.static.validateContent( linktext );
}, function ( doc, range, linktext ) {
var annotations = doc.data.getAnnotationsFromRange( range ),
data = new ve.dm.ElementLinearData( annotations.store, [
{
type: 'link/mwMagic',
attributes: {
content: linktext
}
},
{
type: '/link/mwMagic'
}
] );
// Apply annotations which covered the range.
// Before we get here #autolink has guaranteed that the annotations
// do not contain any link annotations.
data.setAnnotationsAtOffset( 0, annotations );
data.setAnnotationsAtOffset( 1, annotations );
return ve.dm.TransactionBuilder.static.newFromReplacement(
doc, range, data.getData()
);
} );
};
/**
* Open either the 'link' or 'linkNode' window, depending on what is selected.
*
* @method
* @return {boolean} Action was executed
*/
ve.ui.MWLinkAction.prototype.open = function () {
var fragment = this.surface.getModel().getFragment(),
selectedNode = fragment.getSelectedNode(),
windowName = 'link';
if ( selectedNode instanceof ve.dm.MWNumberedExternalLinkNode ) {
windowName = 'linkNode';
} else if ( selectedNode instanceof ve.dm.MWMagicLinkNode ) {
windowName = 'linkMagicNode';
}
this.surface.execute( 'window', 'open', windowName );
return true;
};
/* Registration */
ve.ui.actionFactory.register( ve.ui.MWLinkAction );
ve.ui.commandRegistry.register(
new ve.ui.Command(
'autolinkMagicLink', ve.ui.MWLinkAction.static.name, 'autolinkMagicLink',
{ supportedSelections: [ 'linear' ] }
)
);
ve.ui.sequenceRegistry.register(
// This regexp doesn't have to be precise; we'll validate the magic
// link in #autolinkMagicLink above.
new ve.ui.Sequence( 'autolinkMagicLink', 'autolinkMagicLink', /\b(RFC|PMID|ISBN)\s+[0-9]([- 0-9]*)[0-9Xx][- ]?$/, 0, true, true )
);