mediawiki-extensions-Visual.../modules/ve-mw/ui/inspectors/ve.ui.MWWikitextLinkAnnotationInspector.js
libraryupgrader d1efaac5b2 build: Updating npm dependencies
* eslint-config-wikimedia: 0.15.0 → 0.15.3
  The following rules are failing and were disabled:
  * mediawiki/class-doc
  * mediawiki/valid-package-file-require

* grunt-banana-checker: 0.8.1 → 0.9.0
* grunt-stylelint: 0.14.0 → 0.15.0
* stylelint-config-wikimedia: 0.9.0 → 0.10.1

Change-Id: If718fe8876cc4721602757d38fe06ab421ce8d80
2020-04-16 22:55:11 +00:00

256 lines
8.1 KiB
JavaScript

/*!
* VisualEditor UserInterface MWWikitextLinkAnnotationInspector class.
*
* @copyright 2011-2020 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* Inspector for applying and editing labeled MediaWiki internal and external links.
*
* @class
* @extends ve.ui.MWLinkAnnotationInspector
*
* @constructor
* @param {Object} [config] Configuration options
*/
ve.ui.MWWikitextLinkAnnotationInspector = function VeUiMWWikitextLinkAnnotationInspector( config ) {
// Parent constructor
ve.ui.MWWikitextLinkAnnotationInspector.super.call( this, config );
};
/* Inheritance */
OO.inheritClass( ve.ui.MWWikitextLinkAnnotationInspector, ve.ui.MWLinkAnnotationInspector );
/* Static properties */
ve.ui.MWWikitextLinkAnnotationInspector.static.name = 'wikitextLink';
ve.ui.MWWikitextLinkAnnotationInspector.static.modelClasses = [];
ve.ui.MWWikitextLinkAnnotationInspector.static.handlesSource = true;
// TODO: Support [[linktrail]]s & [[pipe trick|]]
ve.ui.MWWikitextLinkAnnotationInspector.static.internalLinkParser = ( function () {
var openLink = '\\[\\[',
closeLink = '\\]\\]',
noCloseLink = '(?:(?!' + closeLink + ').)*',
noCloseLinkOrPipe = '(?:(?!' + closeLink + ')[^|])*';
return new RegExp(
openLink +
'(' + noCloseLinkOrPipe + ')' +
'(?:\\|(' + noCloseLink + '))?' +
closeLink,
'g'
);
}() );
/* Methods */
/**
* @inheritdoc
*/
ve.ui.MWWikitextLinkAnnotationInspector.prototype.getSetupProcess = function ( data ) {
// Annotation inspector stages the annotation, so call its parent
// Call grand-parent
return ve.ui.AnnotationInspector.super.prototype.getSetupProcess.call( this, data )
.next( function () {
var text, matches, matchTitle, range, contextFragment, contextRange, linkMatches, linkRange, title, namespaceId,
inspectorTitle,
wgNamespaceIds = mw.config.get( 'wgNamespaceIds' ),
internalLinkParser = this.constructor.static.internalLinkParser,
fragment = this.getFragment();
// Only supports linear selections
if ( !( this.initialFragment && this.initialFragment.getSelection() instanceof ve.dm.LinearSelection ) ) {
return ve.createDeferred().reject().promise();
}
// Initialize range
if ( !data.noExpand ) {
if ( !fragment.getSelection().isCollapsed() ) {
// Trim whitespace
fragment = fragment.trimLinearSelection();
}
// Expand to existing link, if present
// Find all links in the paragraph and see which one contains
// the current selection.
contextFragment = fragment.expandLinearSelection( 'siblings' );
contextRange = contextFragment.getSelection().getCoveringRange();
range = fragment.getSelection().getCoveringRange();
text = contextFragment.getText();
internalLinkParser.lastIndex = 0;
while ( ( matches = internalLinkParser.exec( text ) ) !== null ) {
matchTitle = mw.Title.newFromText( matches[ 1 ] );
if ( !matchTitle ) {
continue;
}
linkRange = new ve.Range(
contextRange.start + matches.index,
contextRange.start + matches.index + matches[ 0 ].length
);
namespaceId = mw.Title.newFromText( matches[ 1 ] ).getNamespaceId();
if (
linkRange.containsRange( range ) && !(
// Ignore File:/Category:, but not :File:/:Category:
(
namespaceId === wgNamespaceIds.file ||
namespaceId === wgNamespaceIds.category
) &&
matches[ 1 ].indexOf( ':' ) !== 0
)
) {
linkMatches = matches;
fragment = fragment.getSurface().getLinearFragment( linkRange );
break;
}
}
}
if ( !linkMatches ) {
if ( !data.noExpand && fragment.getSelection().isCollapsed() ) {
// expand to nearest word
fragment = fragment.expandLinearSelection( 'word' );
} else {
// Trim whitespace
fragment = fragment.trimLinearSelection();
}
}
// Update selection
fragment.select();
this.initialSelection = fragment.getSelection();
this.fragment = fragment;
this.initialLabelText = this.fragment.getText();
if ( linkMatches ) {
// Group 1 is the link target, group 2 is the label after | if present
title = mw.Title.newFromText( linkMatches[ 1 ] );
this.initialLabelText = linkMatches[ 2 ] || linkMatches[ 1 ];
// HACK: Remove escaping probably added by this tool.
// We should really do a full parse from wikitext to HTML if
// we see any syntax
this.initialLabelText = this.initialLabelText.replace( /<nowiki>(\]{2,})<\/nowiki>/g, '$1' );
} else {
title = mw.Title.newFromText( this.initialLabelText );
}
if ( title ) {
this.initialAnnotation = this.newInternalLinkAnnotationFromTitle( title );
}
inspectorTitle = ve.msg(
this.isReadOnly() ?
'visualeditor-linkinspector-title' : (
!linkMatches ?
'visualeditor-linkinspector-title-add' :
'visualeditor-linkinspector-title-edit'
)
);
this.title.setLabel( inspectorTitle ).setTitle( inspectorTitle );
this.annotationInput.setReadOnly( this.isReadOnly() );
this.actions.setMode( this.getMode() );
this.linkTypeIndex.setTabPanel(
this.initialAnnotation instanceof ve.dm.MWExternalLinkAnnotation ? 'external' : 'internal'
);
this.annotationInput.setAnnotation( this.initialAnnotation );
this.updateActions();
}, this );
};
/**
* @inheritdoc
*/
ve.ui.MWWikitextLinkAnnotationInspector.prototype.getTeardownProcess = function ( data ) {
data = data || {};
// Call grand-parent
return ve.ui.FragmentInspector.prototype.getTeardownProcess.call( this, data )
.first( function () {
var insert, labelText, labelTitle, targetText, targetTitle, namespaceId, prefix,
wgNamespaceIds = mw.config.get( 'wgNamespaceIds' ),
annotation = this.getAnnotation(),
fragment = this.getFragment(),
insertion = this.getInsertionText();
if ( data && data.action === 'done' && annotation ) {
insert = this.initialSelection.isCollapsed() && insertion.length;
if ( insert ) {
fragment.insertContent( insertion );
labelText = insertion;
} else {
labelText = this.initialLabelText;
}
// Build internal links locally
if ( annotation instanceof ve.dm.MWInternalLinkAnnotation ) {
if ( labelText.indexOf( ']]' ) !== -1 ) {
labelText = labelText.replace( /(\]{2,})/g, '<nowiki>$1</nowiki>' );
}
labelTitle = mw.Title.newFromText( labelText );
if ( !labelTitle || labelTitle.getPrefixedText() !== annotation.getAttribute( 'normalizedTitle' ) ) {
targetText = annotation.getAttribute( 'normalizedTitle' ) + '|';
} else {
targetText = '';
}
targetTitle = mw.Title.newFromText( annotation.getAttribute( 'normalizedTitle' ) );
namespaceId = targetTitle.getNamespaceId();
if (
( targetText + labelText )[ 0 ] !== ':' && (
namespaceId === wgNamespaceIds.file ||
namespaceId === wgNamespaceIds.category
)
) {
prefix = ':';
} else {
prefix = '';
}
fragment.insertContent( '[[' + prefix + targetText + labelText + ']]' );
} else {
// Annotating the surface will send the content to Parsoid before
// it is inserted into the wikitext document. It is slower but it
// will handle all cases.
// Where possible we should generate the wikitext locally.
fragment.annotateContent( 'set', annotation );
}
// Fix selection after annotating is complete
fragment.getPending().then( function () {
if ( insert ) {
fragment.collapseToEnd().select();
} else {
fragment.select();
}
} );
} else if ( !data.action ) {
// Restore selection to what it was before we expanded it
this.initialFragment.select();
}
}, this )
.next( function () {
// Reset state
this.initialSelection = null;
this.initialAnnotation = null;
// Parent resets
this.allowProtocolInInternal = false;
this.internalAnnotationInput.setAnnotation( null );
this.externalAnnotationInput.setAnnotation( null );
}, this );
};
/* Registration */
ve.ui.windowFactory.register( ve.ui.MWWikitextLinkAnnotationInspector );
ve.ui.wikitextCommandRegistry.register(
new ve.ui.Command(
'link', 'window', 'open',
{ args: [ 'wikitextLink' ], supportedSelections: [ 'linear' ] }
)
);