mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-11-28 08:10:35 +00:00
Make autonumbered external links inspectable
When the target of an autonumbered link is changed to a URL, it's kept as an autonumbered link and its target is updated. When the target is changed to a MediaWiki page, the autonumbered link is removed and replaced with an internal link with the text set to the target. So for instance, if you inspect [http://www.example.com] and change its target to "Foo", the result will be [[Foo]]. The core of this commit adds support for inspecting nodes to ve.ui.LinkInspector. This support should probably move into a class in between AnnotationInspector and LinkInspector, perhaps called HybridInspector or something, but I'm deferring that for now. LinkInspector allows changes to inspected nodes to be reflected either as attribute changes on the node, or by replacing the node with something else. MWLinkInspector uses this feature to replace the autonumbered external link node with an internal link annotation when the target is set to an external link. Bug: 53505 Change-Id: Icb404af84c24574438e4de3ef05bbd1993b593f7
This commit is contained in:
parent
e41cb61814
commit
93b645a453
|
@ -509,6 +509,7 @@ $wgResourceModules += array(
|
||||||
've-mw/ui/tools/ve.ui.MWFormatTool.js',
|
've-mw/ui/tools/ve.ui.MWFormatTool.js',
|
||||||
've-mw/ui/tools/ve.ui.MWDialogTool.js',
|
've-mw/ui/tools/ve.ui.MWDialogTool.js',
|
||||||
've-mw/ui/tools/ve.ui.MWPopupTool.js',
|
've-mw/ui/tools/ve.ui.MWPopupTool.js',
|
||||||
|
've-mw/ui/tools/ve.ui.MWInspectorTool.js',
|
||||||
|
|
||||||
've/ui/inspectors/ve.ui.AnnotationInspector.js',
|
've/ui/inspectors/ve.ui.AnnotationInspector.js',
|
||||||
've/ui/inspectors/ve.ui.LinkInspector.js',
|
've/ui/inspectors/ve.ui.LinkInspector.js',
|
||||||
|
|
|
@ -31,7 +31,9 @@ OO.inheritClass( ve.ui.MWLinkInspector, ve.ui.LinkInspector );
|
||||||
ve.ui.MWLinkInspector.static.name = 'link';
|
ve.ui.MWLinkInspector.static.name = 'link';
|
||||||
|
|
||||||
ve.ui.MWLinkInspector.static.modelClasses = [
|
ve.ui.MWLinkInspector.static.modelClasses = [
|
||||||
ve.dm.MWExternalLinkAnnotation, ve.dm.MWInternalLinkAnnotation
|
ve.dm.MWExternalLinkAnnotation,
|
||||||
|
ve.dm.MWInternalLinkAnnotation,
|
||||||
|
ve.dm.MWNumberedExternalLinkNode
|
||||||
];
|
];
|
||||||
|
|
||||||
ve.ui.MWLinkInspector.static.linkTargetInputWidget = ve.ui.MWLinkTargetInputWidget;
|
ve.ui.MWLinkInspector.static.linkTargetInputWidget = ve.ui.MWLinkTargetInputWidget;
|
||||||
|
@ -85,6 +87,27 @@ ve.ui.MWLinkInspector.prototype.getAnnotationFromText = function ( target ) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
ve.ui.MWLinkInspector.prototype.getNodeChanges = function () {
|
||||||
|
var annotations, data,
|
||||||
|
doc = this.linkNode.getDocument(),
|
||||||
|
annotation = this.getAnnotation();
|
||||||
|
if ( annotation instanceof ve.dm.MWInternalLinkAnnotation ) {
|
||||||
|
// We're inspecting a numbered external link node and attempting to set its target
|
||||||
|
// to an internal link. Replace the numbered link node with an internal link annotation,
|
||||||
|
// with the link target as the text.
|
||||||
|
annotations = doc.data.getAnnotationsFromOffset( this.linkNode.getOffset() ).clone();
|
||||||
|
annotations.push( annotation );
|
||||||
|
data = ve.splitClusters( annotation.getAttribute( 'title' ) );
|
||||||
|
ve.dm.Document.static.addAnnotationsToData( data, annotations );
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
// Parent method
|
||||||
|
return ve.ui.LinkInspector.prototype.getNodeChanges.call( this );
|
||||||
|
};
|
||||||
|
|
||||||
/* Registration */
|
/* Registration */
|
||||||
|
|
||||||
ve.ui.inspectorFactory.register( ve.ui.MWLinkInspector );
|
ve.ui.inspectorFactory.register( ve.ui.MWLinkInspector );
|
||||||
|
|
28
modules/ve-mw/ui/tools/ve.ui.MWInspectorTool.js
Normal file
28
modules/ve-mw/ui/tools/ve.ui.MWInspectorTool.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
/*!
|
||||||
|
* VisualEditor UserInterface MediaWiki InspectorTool classes.
|
||||||
|
*
|
||||||
|
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
|
||||||
|
* @license The MIT License (MIT); see LICENSE.txt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UserInterface link tool.
|
||||||
|
*
|
||||||
|
* @class
|
||||||
|
* @extends ve.ui.LinkInspectorTool
|
||||||
|
* @constructor
|
||||||
|
* @param {OO.ui.ToolGroup} toolGroup
|
||||||
|
* @param {Object} [config] Configuration options
|
||||||
|
*/
|
||||||
|
ve.ui.MWLinkInspectorTool = function VeUiMWLinkInspectorTool( toolGroup, config ) {
|
||||||
|
ve.ui.LinkInspectorTool.call( this, toolGroup, config );
|
||||||
|
};
|
||||||
|
|
||||||
|
OO.inheritClass( ve.ui.MWLinkInspectorTool, ve.ui.LinkInspectorTool );
|
||||||
|
|
||||||
|
ve.ui.MWLinkInspectorTool.static.modelClasses =
|
||||||
|
ve.ui.MWLinkInspectorTool.static.modelClasses.concat(
|
||||||
|
[ ve.dm.MWNumberedExternalLinkNode ]
|
||||||
|
);
|
||||||
|
|
||||||
|
ve.ui.toolFactory.register( ve.ui.MWLinkInspectorTool );
|
|
@ -18,6 +18,9 @@
|
||||||
ve.ui.LinkInspector = function VeUiLinkInspector( windowSet, config ) {
|
ve.ui.LinkInspector = function VeUiLinkInspector( windowSet, config ) {
|
||||||
// Parent constructor
|
// Parent constructor
|
||||||
ve.ui.AnnotationInspector.call( this, windowSet, config );
|
ve.ui.AnnotationInspector.call( this, windowSet, config );
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
this.linkNode = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Inheritance */
|
/* Inheritance */
|
||||||
|
@ -35,7 +38,11 @@ ve.ui.LinkInspector.static.titleMessage = 'visualeditor-linkinspector-title';
|
||||||
ve.ui.LinkInspector.static.linkTargetInputWidget = ve.ui.LinkTargetInputWidget;
|
ve.ui.LinkInspector.static.linkTargetInputWidget = ve.ui.LinkTargetInputWidget;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Annotation models this inspector can edit.
|
* Models this inspector can edit.
|
||||||
|
*
|
||||||
|
* These models may be either annotations or nodes. When nodes are inspected, #getNodeChanges
|
||||||
|
* is used instead of #getAnnotation to determine what changes to save when the inspector is
|
||||||
|
* closed.
|
||||||
*
|
*
|
||||||
* @static
|
* @static
|
||||||
* @property {Function[]}
|
* @property {Function[]}
|
||||||
|
@ -64,6 +71,20 @@ ve.ui.LinkInspector.prototype.getAnnotationFromText = function ( text ) {
|
||||||
return new ve.dm.LinkAnnotation( { 'type': 'link', 'attributes': { 'href': text } } );
|
return new ve.dm.LinkAnnotation( { 'type': 'link', 'attributes': { 'href': text } } );
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the changes to make to the node that's currently being inspected.
|
||||||
|
*
|
||||||
|
* This function can either return a plain object with attribute changes to make to the node,
|
||||||
|
* or an array with linear model data to replace the node with.
|
||||||
|
*
|
||||||
|
* This function will only be invoked if this.linkNode is set.
|
||||||
|
*
|
||||||
|
* @returns {Object|Array} Object with attribute changes, or linear model array
|
||||||
|
*/
|
||||||
|
ve.ui.LinkInspector.prototype.getNodeChanges = function () {
|
||||||
|
return { 'href': this.targetInput.annotation.getAttribute( 'href' ) };
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
|
@ -84,10 +105,19 @@ ve.ui.LinkInspector.prototype.initialize = function () {
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
ve.ui.LinkInspector.prototype.setup = function ( data ) {
|
ve.ui.LinkInspector.prototype.setup = function ( data ) {
|
||||||
// Parent method
|
var focusedNode = this.surface.getView().getFocusedNode();
|
||||||
ve.ui.AnnotationInspector.prototype.setup.call( this, data );
|
if ( focusedNode && ve.isInstanceOfAny( focusedNode.getModel(), this.constructor.static.modelClasses ) ) {
|
||||||
|
this.linkNode = focusedNode.getModel();
|
||||||
|
|
||||||
// Wait for animation to complete
|
// Call grandparent method, skipping AnnotationInspector
|
||||||
|
ve.ui.Inspector.prototype.setup.call( this, data );
|
||||||
|
} else {
|
||||||
|
this.linkNode = null;
|
||||||
|
// Parent method
|
||||||
|
ve.ui.AnnotationInspector.prototype.setup.call( this, data );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable surface until animation is complete; will be reenabled in ready()
|
||||||
this.surface.disable();
|
this.surface.disable();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -100,8 +130,11 @@ ve.ui.LinkInspector.prototype.ready = function () {
|
||||||
|
|
||||||
// Note: Focus input prior to setting target annotation
|
// Note: Focus input prior to setting target annotation
|
||||||
this.targetInput.$input.focus();
|
this.targetInput.$input.focus();
|
||||||
// Setup annotation
|
// Setup targetInput contents
|
||||||
if ( this.initialAnnotation ) {
|
if ( this.linkNode ) {
|
||||||
|
// FIXME this assumes the linkNode will always have an href attribute
|
||||||
|
this.targetInput.setValue( this.linkNode.getAttribute( 'href' ) );
|
||||||
|
} else if ( this.initialAnnotation ) {
|
||||||
this.targetInput.setAnnotation( this.initialAnnotation );
|
this.targetInput.setAnnotation( this.initialAnnotation );
|
||||||
} else {
|
} else {
|
||||||
// If an initial annotation couldn't be created (e.g. the text was invalid),
|
// If an initial annotation couldn't be created (e.g. the text was invalid),
|
||||||
|
@ -112,6 +145,54 @@ ve.ui.LinkInspector.prototype.ready = function () {
|
||||||
this.surface.enable();
|
this.surface.enable();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
ve.ui.LinkInspector.prototype.teardown = function ( data ) {
|
||||||
|
var changes, remove, replace, nodeRange, surfaceModel = this.surface.getModel();
|
||||||
|
if ( this.linkNode ) {
|
||||||
|
nodeRange = this.linkNode.getOuterRange();
|
||||||
|
changes = this.getNodeChanges();
|
||||||
|
replace = ve.isArray( changes );
|
||||||
|
// FIXME figure out a better way to do the "if input is empty, remove" thing,
|
||||||
|
// not duplicating it here from AnnotationInspector (where it doesn't even belong)
|
||||||
|
remove = data.action === 'remove' || this.targetInput.getValue() === '';
|
||||||
|
if ( remove || replace ) {
|
||||||
|
surfaceModel.change(
|
||||||
|
ve.dm.Transaction.newFromRemoval(
|
||||||
|
surfaceModel.getDocument(),
|
||||||
|
nodeRange
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ( !remove ) {
|
||||||
|
if ( replace ) {
|
||||||
|
// We've already removed the node, so we just need to do an insertion now
|
||||||
|
surfaceModel.change(
|
||||||
|
ve.dm.Transaction.newFromInsertion(
|
||||||
|
surfaceModel.getDocument(),
|
||||||
|
nodeRange.start,
|
||||||
|
changes
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
surfaceModel.change(
|
||||||
|
ve.dm.Transaction.newFromAttributeChanges(
|
||||||
|
surfaceModel.getDocument(),
|
||||||
|
nodeRange.start,
|
||||||
|
changes
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Call grandparent method, skipping AnnotationInspector
|
||||||
|
ve.ui.Inspector.prototype.teardown.call( this, data );
|
||||||
|
} else {
|
||||||
|
// Parent method
|
||||||
|
ve.ui.AnnotationInspector.prototype.teardown.call( this, data );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/* Registration */
|
/* Registration */
|
||||||
|
|
||||||
ve.ui.inspectorFactory.register( ve.ui.LinkInspector );
|
ve.ui.inspectorFactory.register( ve.ui.LinkInspector );
|
||||||
|
|
Loading…
Reference in a new issue