mediawiki-extensions-Visual.../modules/ve/ui/inspectors/ve.ui.LinkInspector.js
Trevor Parscal 1572ec1569 Context, frame, window, dialog and inspector refactor
This is a major refactor of user interface context, frame, dialog
and inspector classes, including adding several new classes which
generalize managing inspectors/dialogs (which are now subclasses
of window).

New classes:
* ve.ui.Window.js - base class for inspector and dialog classes
* ve.ui.WindowSet.js - manages mutually exclusive windows, used
  by surface and context for dialogs and inspectors respectively
* ve.ui.DialogFactory - generates dialogs
* ve.ui.IconButtonWidget - used in inspector for buttons in the head

Refactored classes:
* ve.ui.Context - moved inspector management to window set
* ve.ui.Frame - made iframes initialize asynchronously
* ve.ui.Dialog and ve.ui.Inspector - moved initialization to async
  initialize method

Other interesting bits:

ve.ui.*Icons*.css, *.svg, *.png, *.ai
* Merged icon stylesheets so all icons are available inside windows
* Renamed inspector icon to window

ve.ui.*.css
* Reorganized styles so that different windows can include only
  what they need
* Moved things to where they belonged (some things were in strange places)

ve.init.Target.js, ve.init.mw.ViewPageTarget.js, ve.init.sa.Target.js
* Removed dialog management - dialogs are managed by the surface now

ve.ui.*Dialog.js
* Renamed title message static property
* Added registration

ve.ui.*Inspector.js
* Switch to accept surface object rather than context, which conforms
  to the more general window class without losing any functionality
  (in fact, most of the time the surface was what we actually wanted)

ve.ui.MenuWidget.js, ve.ui.MWLinkTargetInputWidget.js
* Using surface overly rather than passing an overlay around
  through constructors

Change-Id: Ifd16a1003ff44c48ee7b2c66928cf9cc858b2564
2013-03-14 00:03:31 +00:00

205 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 );
// Setup annotation
this.initialAnnotationHash = annotation && annotation.getHash();
this.targetInput.setAnnotation( annotation );
// Set focus on the location input
setTimeout( ve.bind( function () {
this.targetInput.$.focus().select();
}, this ) );
};
/**
* 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 );