mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-11-29 16:44:51 +00:00
bf254f44da
Objective: Make it possible for inspectors to inspect nodes or annotations, rather than only annotations. Meanwhile, also make it possible for dialogs to edit an annotation. Strategy: Switch from using type patterns to associate inspectors with annotations to using arrays of classes, similar to how dialogs already work. Introduce a view registry which provides lookups for relationships between models and views. This is more centralized and less repetitive than implement matching functions for both annotations and nodes in both the dialog and inspector factories. Changes: *.php * Added links to new file ve.AnnotationAction.js * Removed unused parameter to filter annotations using a string or regexp ve.dm.AnnotationSet.js * Switched from property/value arguments to callbacks ve.ui.*(Dialog|Inspector).js * Replaced type patterns with class lists * Added class to view registry ve.ui.*Tool.js, ve.ui.Context.js * Updated model/view relationship lookup ve.ui.*Factory.js * Removed overly-specific lookup functions ve.ui.Inspector.js * Removed typePattern property * Updated model/view relationship lookup ve.ui.ViewRegistry.js * New class! * Migrated node and annotation lookup functions from factories Change-Id: Ic2bbcf072fdd87e5ce8a03fe1ae3e6d8d50e2593
232 lines
6 KiB
JavaScript
232 lines
6 KiB
JavaScript
/*!
|
|
* VisualEditor UserInterface LinkInspector class.
|
|
*
|
|
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
*/
|
|
|
|
/**
|
|
* UserInterface link inspector.
|
|
*
|
|
* @class
|
|
* @extends ve.ui.Inspector
|
|
*
|
|
* @constructor
|
|
* @param {ve.Surface} surface
|
|
*/
|
|
ve.ui.LinkInspector = function VeUiLinkInspector( surface ) {
|
|
// Parent constructor
|
|
ve.ui.Inspector.call( this, surface );
|
|
|
|
// Properties
|
|
this.initialAnnotationHash = null;
|
|
this.isNewAnnotation = false;
|
|
};
|
|
|
|
/* Inheritance */
|
|
|
|
ve.inheritClass( ve.ui.LinkInspector, ve.ui.Inspector );
|
|
|
|
/* Static properties */
|
|
|
|
ve.ui.LinkInspector.static.icon = 'link';
|
|
|
|
ve.ui.LinkInspector.static.titleMessage = 'visualeditor-linkinspector-title';
|
|
|
|
ve.ui.LinkInspector.static.modelClasses = [ ve.dm.LinkAnnotation ];
|
|
|
|
ve.ui.LinkInspector.static.linkTargetInputWidget = ve.ui.LinkTargetInputWidget;
|
|
|
|
/* Methods */
|
|
|
|
/**
|
|
* Handle frame ready events.
|
|
*
|
|
* @method
|
|
*/
|
|
ve.ui.LinkInspector.prototype.initialize = function () {
|
|
// Call parent method
|
|
ve.ui.Inspector.prototype.initialize.call( this );
|
|
|
|
// Properties
|
|
this.targetInput = new this.constructor.static.linkTargetInputWidget( {
|
|
'$$': this.$$, '$overlay': this.surface.$overlay
|
|
} );
|
|
|
|
// Initialization
|
|
this.$form.append( this.targetInput.$ );
|
|
};
|
|
|
|
/**
|
|
* Handle the inspector being setup.
|
|
*
|
|
* There are 4 scenarios:
|
|
* * Zero-length selection not near a word -> no change, text will be inserted on close
|
|
* * Zero-length selection inside or adjacent to a word -> expand selection to cover word
|
|
* * Selection covering non-link text -> trim selection to remove leading/trailing whitespace
|
|
* * Selection covering link text -> expand selection to cover link
|
|
*
|
|
* @method
|
|
*/
|
|
ve.ui.LinkInspector.prototype.onSetup = function () {
|
|
var expandedFragment, trimmedFragment, truncatedFragment,
|
|
fragment = this.surface.getModel().getFragment( null, true ),
|
|
annotation = this.getMatchingAnnotations( fragment ).get( 0 );
|
|
|
|
// Call parent method
|
|
ve.ui.Inspector.prototype.onSetup.call( this );
|
|
|
|
// Initialize range
|
|
if ( !annotation ) {
|
|
if ( fragment.getRange().isCollapsed() ) {
|
|
// Expand to nearest word
|
|
expandedFragment = fragment.expandRange( 'word' );
|
|
fragment.destroy();
|
|
fragment = expandedFragment;
|
|
} else {
|
|
// Trim whitespace
|
|
trimmedFragment = fragment.trimRange();
|
|
fragment.destroy();
|
|
fragment = trimmedFragment;
|
|
}
|
|
if ( !fragment.getRange().isCollapsed() ) {
|
|
// Create annotation from selection
|
|
truncatedFragment = fragment.truncateRange( 255 );
|
|
fragment.destroy();
|
|
fragment = truncatedFragment;
|
|
annotation = this.getAnnotationFromTarget( fragment.getText() );
|
|
fragment.annotateContent( 'set', annotation );
|
|
this.isNewAnnotation = true;
|
|
}
|
|
} else {
|
|
// Expand range to cover annotation
|
|
expandedFragment = fragment.expandRange( 'annotation', annotation );
|
|
fragment.destroy();
|
|
fragment = expandedFragment;
|
|
}
|
|
|
|
// Update selection
|
|
fragment.select().destroy();
|
|
};
|
|
|
|
/**
|
|
* Handle the inspector being opened.
|
|
*
|
|
* @method
|
|
*/
|
|
ve.ui.LinkInspector.prototype.onOpen = function () {
|
|
var fragment = this.surface.getModel().getFragment( null, true ),
|
|
annotation = this.getMatchingAnnotations( fragment ).get( 0 );
|
|
|
|
// Call parent method
|
|
ve.ui.Inspector.prototype.onOpen.call( this );
|
|
|
|
// Wait for animation to complete
|
|
setTimeout( ve.bind( function () {
|
|
// Setup annotation
|
|
this.initialAnnotationHash = annotation && ve.getHash( annotation );
|
|
this.targetInput.setAnnotation( annotation );
|
|
this.targetInput.$input.focus().select();
|
|
}, this ), 200 );
|
|
|
|
fragment.destroy();
|
|
};
|
|
|
|
/**
|
|
* Handle the inspector being opened.
|
|
*
|
|
* @method
|
|
* @param {string} action Action that caused the window to be closed
|
|
*/
|
|
ve.ui.LinkInspector.prototype.onClose = function ( action ) {
|
|
// Call parent method
|
|
ve.ui.Inspector.prototype.onClose.call( this, action );
|
|
|
|
var i, len, annotations, selection, adjustedFragment,
|
|
insert = false,
|
|
undo = false,
|
|
clear = false,
|
|
set = false,
|
|
remove = action === 'remove',
|
|
target = this.targetInput.getValue(),
|
|
annotation = this.targetInput.getAnnotation(),
|
|
fragment = this.surface.getModel().getFragment( this.initialSelection, false );
|
|
// Undefined annotation causes removal
|
|
if ( !annotation ) {
|
|
remove = true;
|
|
}
|
|
if ( remove ) {
|
|
clear = true;
|
|
} else {
|
|
if ( this.initialSelection.isCollapsed() ) {
|
|
insert = true;
|
|
}
|
|
if ( ve.getHash( annotation ) !== this.initialAnnotationHash ) {
|
|
if ( this.isNewAnnotation ) {
|
|
undo = true;
|
|
} else {
|
|
clear = true;
|
|
}
|
|
set = true;
|
|
}
|
|
}
|
|
if ( insert ) {
|
|
// Insert default text and select it
|
|
fragment.insertContent( target, false );
|
|
adjustedFragment = fragment.adjustRange( -target.length, 0 );
|
|
fragment.destroy();
|
|
fragment = adjustedFragment;
|
|
|
|
// Move cursor to the end of the inserted content
|
|
selection = new ve.Range( this.initialSelection.start + target.length );
|
|
}
|
|
if ( undo ) {
|
|
// Go back to before we added an annotation
|
|
this.surface.execute( 'history', 'undo' );
|
|
}
|
|
if ( clear ) {
|
|
// Clear all existing annotations
|
|
annotations = this.getMatchingAnnotations( fragment ).get();
|
|
for ( i = 0, len = annotations.length; i < len; i++ ) {
|
|
fragment.annotateContent( 'clear', annotations[i] );
|
|
}
|
|
}
|
|
if ( set ) {
|
|
// Apply new annotation
|
|
fragment.annotateContent( 'set', annotation );
|
|
}
|
|
if ( action === 'back' ) {
|
|
selection = this.previousSelection;
|
|
}
|
|
// Selection changes may have occured in the insertion and annotation hullabaloo - restore it
|
|
this.surface.execute(
|
|
'content', 'select', selection || new ve.Range( fragment.getRange().end )
|
|
);
|
|
// Reset state
|
|
this.isNewAnnotation = false;
|
|
|
|
fragment.destroy();
|
|
};
|
|
|
|
/**
|
|
* Get an annotation object from a target.
|
|
*
|
|
* @method
|
|
* @param {string} target Link target
|
|
* @returns {ve.dm.LinkAnnotation}
|
|
*/
|
|
ve.ui.LinkInspector.prototype.getAnnotationFromTarget = function ( target ) {
|
|
return new ve.dm.LinkAnnotation( {
|
|
'type': 'link',
|
|
'attributes': {
|
|
'href': target
|
|
}
|
|
} );
|
|
};
|
|
|
|
/* Registration */
|
|
|
|
ve.ui.inspectorFactory.register( 'link', ve.ui.LinkInspector );
|
|
|
|
ve.ui.viewRegistry.register( 'link', ve.ui.LinkInspector );
|