mediawiki-extensions-Visual.../modules/ve/ui/inspectors/ve.ui.AnnotationInspector.js
Trevor Parscal a4491c12f8 Make link inspector re-usable by splitting it up
Objective:

* Make the majority of link inspector, which is generic to any annotation,
  usable for other annotation inspectors

This was merged earlier (f7107fa20d) but broke master, so it was
reverted (092fa74dee). This commit also incorporates 5dcf5d1c49.

Change-Id: Ib9190dee66ce064d69962f9c4c5b3a710be8ad07
2013-07-12 17:58:04 +00:00

205 lines
5.8 KiB
JavaScript

/*!
* VisualEditor UserInterface AnnotationInspector class.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* Annotation inspector.
*
* @class
* @abstract
* @extends ve.ui.Inspector
*
* @constructor
* @param {ve.ui.Surface} surface
* @param {Object} [config] Config options
*/
ve.ui.AnnotationInspector = function VeUiAnnotationInspector( surface, config ) {
// Parent constructor
ve.ui.Inspector.call( this, surface, config );
// Properties
this.initialAnnotation = null;
this.initialAnnotationHash = null;
this.isNewAnnotation = false;
};
/* Inheritance */
ve.inheritClass( ve.ui.AnnotationInspector, ve.ui.Inspector );
/**
* Annotation models this inspector can edit.
*
* @static
* @inheritable
* @property {Function[]}
*/
ve.ui.AnnotationInspector.static.modelClasses = [];
/* Methods */
/**
* 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-annotated text -> trim selection to remove leading/trailing whitespace
* - Selection covering annotated text -> expand selection to cover annotation
*
* @method
*/
ve.ui.AnnotationInspector.prototype.onSetup = function () {
var expandedFragment, trimmedFragment, truncatedFragment,
fragment = this.surface.getModel().getFragment( null, true ),
annotation = this.getMatchingAnnotations( fragment, true ).get( 0 );
// 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 = expandedFragment;
} else {
// Trim whitespace
trimmedFragment = fragment.trimRange();
fragment = trimmedFragment;
}
if ( !fragment.getRange().isCollapsed() ) {
// Create annotation from selection
truncatedFragment = fragment.truncateRange( 255 );
fragment = truncatedFragment;
annotation = this.getAnnotationFromText( fragment.getText() );
fragment.annotateContent( 'set', annotation );
this.isNewAnnotation = true;
}
} else {
// Expand range to cover annotation
expandedFragment = fragment.expandRange( 'annotation', annotation );
fragment = expandedFragment;
}
// Update selection
fragment.select();
};
/**
* Handle the inspector being opened.
*/
ve.ui.AnnotationInspector.prototype.onOpen = function () {
var fragment = this.surface.getModel().getFragment( null, true ),
// Note that we don't set the 'all' flag here so that any
// non-annotated content is annotated on close
initialAnnotation = this.getMatchingAnnotations( fragment ).get( 0 );
// Parent method
ve.ui.Inspector.prototype.onOpen.call( this );
// Initialization
this.initialAnnotation = initialAnnotation;
this.initialAnnotationHash = initialAnnotation ? ve.getHash( initialAnnotation ) : null;
};
/**
* Handle the inspector being closed.
*
* @param {string} action Action that caused the window to be closed
*/
ve.ui.AnnotationInspector.prototype.onClose = function ( action ) {
// Parent method
ve.ui.Inspector.prototype.onClose.call( this, action );
var i, len, annotations, selection,
insert = false,
undo = false,
clear = false,
set = false,
target = this.targetInput.getValue(),
annotation = this.targetInput.getAnnotation(),
remove = action === 'remove' && !!annotation,
fragment = this.surface.getModel().getFragment( this.initialSelection, false );
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 ) {
fragment.insertContent( target, false );
// Move cursor to the end of the inserted content, even if back button is used
this.previousSelection = 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, true ).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;
};
/**
* Get an annotation object from text.
*
* @method
* @abstract
* @param {string} text Content text
* @returns {ve.dm.Annotation}
* @throws {Error} If not overriden in a subclass
*/
ve.ui.AnnotationInspector.prototype.getAnnotationFromText = function () {
throw new Error(
've.ui.AnnotationInspector.getAnnotationFromText not implemented in subclass'
);
};
/**
* Get matching annotations within a fragment.
*
* @method
* @param {ve.dm.SurfaceFragment} fragment Fragment to get matching annotations within
* @param {boolean} [all] Get annotations which only cover some of the fragment
* @returns {ve.dm.AnnotationSet} Matching annotations
*/
ve.ui.AnnotationInspector.prototype.getMatchingAnnotations = function ( fragment, all ) {
var modelClasses = this.constructor.static.modelClasses;
return fragment.getAnnotations( all ).filter( function ( annnotation ) {
return ve.isInstanceOfAny( annnotation, modelClasses );
} );
};