mediawiki-extensions-Visual.../modules/ve/ui/inspectors/ve.ui.LinkInspector.js
Trevor Parscal ec912dc2d1 Link inspector menu not appearing in the right place
ve.ui.Widget.css
* Adjust menu up a few pixels to match other uses of ve.ui.MenuWidget (the format drop down)

ve.ui.LinkInspector.js
* Moved the form value initialization to a timeout that fires well after the animation of the inspector - this is only important because the first element has a menu that pops up and the menu was rendering in the wrong location

ve.ui.Frame.js
* Added reference to frame within this.$$ by passing it to get$$

ve.ui.Inspector.js
* Removed auto-focus on open from inspector base class - this will be done in a more specific and controlled way instead

ve.ui.js
* Added optional frame argument to get$$ so that it's easy to get the frame from any $$ that's bound to an iframe document

ve.ui.Window.js
* Removed duplicate static member assignments
* Added auto-focus on the window's frame before calling onOpen
* Added auto-blur of anything focused within the iframe after calling onClose

ve.ui.MWLinkTargetInputWidget.js
* Auto-highlight the selected item when populating a menu so that pressing enter always keeps the currently selected item selected

ve.ui.TextInputMenuWidget.js
* Take the frame's position into account when positioning a menu below an input

Change-Id: I334f7db29af6b821bcfc8dc3c0ccba2636d4d9b1
2013-03-14 16:56:04 -07:00

204 lines
5.3 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.typePattern = /^link(\/|$)/;
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 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
fragment = fragment.expandRange( 'word' );
} else {
// Trim whitespace
fragment = fragment.trimRange();
}
if ( !fragment.getRange().isCollapsed() ) {
// Create annotation from selection
annotation = this.getAnnotationFromTarget( fragment.truncateRange( 255 ).getText() );
fragment.annotateContent( 'set', annotation );
this.isNewAnnotation = true;
}
} else {
// Expand range to cover annotation
fragment = fragment.expandRange( 'annotation', annotation );
}
// Update selection
fragment.select();
};
/**
* 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 && annotation.getHash();
this.targetInput.setAnnotation( annotation );
this.targetInput.$input.focus().select();
}, this ), 200 );
};
/**
* Handle the inspector being opened.
*
* @method
* @param {boolean} remove Annotation should be removed
*/
ve.ui.LinkInspector.prototype.onClose = function ( remove ) {
// Call parent method
ve.ui.Inspector.prototype.onClose.call( this );
var i, len, annotations, selection,
insert = false,
undo = false,
clear = false,
set = false,
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 ( annotation.getHash() !== this.initialAnnotationHash ) {
if ( this.isNewAnnotation ) {
undo = true;
} else {
clear = true;
}
set = true;
}
}
if ( insert ) {
// Insert default text and select it
fragment = fragment.insertContent( target, false ).adjustRange( -target.length, 0 );
// 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 );
}
// 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;
};
/**
* 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( { 'href': target } );
};
/* Registration */
ve.ui.inspectorFactory.register( 'link', ve.ui.LinkInspector );