mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/VisualEditor
synced 2024-11-25 14:56:20 +00:00
9c74c97808
ve.dm.SurfaceFragment * Removed flawed implementation of word mode for expandRange method and made use of new getNearestWordBoundary method in the document model ve.dm.Surface * Got rid of useInsertionAnnotations, which allowed disabling and enabling of insertion annotations - this isn't needed anymore because it was just a dirty hack around the improper starting and stopping of surface observer that's now solved more elegantly by emitting lock and unlock before committing or rolling back transactions * Get annotations from the first character of the selection if the selection is not collapsed * Only emit annotationChange events if it really changed ve.dm.Document * Added getNearestWordBoundary method which performs the work behind the surface fragment expandRange word method ve.ce.SurfaceObserver * Allow using an initial selection to avoid the observer thinking the selection has changed just because it started out with null * Only emit selectionChange event if there was a meaningful change ve.ce.Surface * (bug 42279) Only annotate characters if insertion annotations are not empty * Remove manual locking and unlocking, this is now done inside the change method of surface model * Provide an initial selection to surface observer when we clear it * Remove enabling and disabling of insertionAnnotations, this isn't needed anymore * Stop/start observer on key presses that execute actions as well as those that have no special handling ve.ce.Document * Make getNodeFromOffsetand getSlugAtOffset return null when given -1 as an offset Change-Id: Ibf6b26de299e54ae8688a2653bf5d5538927f8c3
194 lines
4.7 KiB
JavaScript
194 lines
4.7 KiB
JavaScript
/*global rangy */
|
|
|
|
/**
|
|
* VisualEditor content editable Surface class.
|
|
*
|
|
* @copyright 2011-2012 VisualEditor Team and others; see AUTHORS.txt
|
|
* @license The MIT License (MIT); see LICENSE.txt
|
|
*/
|
|
|
|
/**
|
|
* Surface observer.
|
|
*
|
|
* @class
|
|
* @constructor
|
|
* @extends {ve.EventEmitter}
|
|
* @param {ve.ce.Document} documentView Document to observe
|
|
*/
|
|
ve.ce.SurfaceObserver = function VeCeSurfaceObserver( documentView ) {
|
|
// Parent constructor
|
|
ve.EventEmitter.call( this );
|
|
|
|
// Properties
|
|
this.documentView = documentView;
|
|
this.polling = false;
|
|
this.timeoutId = null;
|
|
this.frequency = 250; //ms
|
|
|
|
// Initialization
|
|
this.clear();
|
|
};
|
|
|
|
/* Inheritance */
|
|
|
|
ve.inheritClass( ve.ce.SurfaceObserver, ve.EventEmitter );
|
|
|
|
/* Methods */
|
|
|
|
/**
|
|
* Clears polling data.
|
|
*
|
|
* @method
|
|
* @param {ve.Range} range Initial range to use
|
|
*/
|
|
ve.ce.SurfaceObserver.prototype.clear = function ( range ) {
|
|
this.rangySelection = {
|
|
anchorNode: null,
|
|
anchorOffset: null,
|
|
focusNode: null,
|
|
focusOffset: null
|
|
};
|
|
this.range = range || null;
|
|
this.node = null;
|
|
this.text = null;
|
|
this.hash = null;
|
|
};
|
|
|
|
/**
|
|
* Starts polling.
|
|
*
|
|
* If {async} is false or undefined the first poll cycle will occur immediately and synchronously.
|
|
*
|
|
* @method
|
|
* @param {Boolean} async Poll the first time asynchronously
|
|
*/
|
|
ve.ce.SurfaceObserver.prototype.start = function ( async ) {
|
|
this.polling = true;
|
|
this.poll( async );
|
|
};
|
|
|
|
/**
|
|
* Stops polling.
|
|
*
|
|
* If {poll} is false or undefined than no final poll cycle will occur and changes can be lost. If
|
|
* it's true then a final poll cycle will occur immediately and synchronously.
|
|
*
|
|
* @method
|
|
* @param {Boolean} poll Poll one last time before stopping future polling
|
|
*/
|
|
ve.ce.SurfaceObserver.prototype.stop = function ( poll ) {
|
|
if ( this.polling === true ) {
|
|
if ( poll === true ) {
|
|
this.poll();
|
|
}
|
|
this.polling = false;
|
|
clearTimeout( this.timeoutId );
|
|
this.timeoutId = null;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Poll for changes.
|
|
*
|
|
* If {async} is false or undefined then polling will occcur asynchronously.
|
|
*
|
|
* TODO: fixing selection in certain cases, handling selection across multiple nodes in Firefox
|
|
*
|
|
* @method
|
|
* @param {Boolean} async Poll asynchronously
|
|
*/
|
|
ve.ce.SurfaceObserver.prototype.poll = function ( async ) {
|
|
var delayPoll, rangySelection, range, node, text, hash;
|
|
|
|
if ( this.timeoutId !== null ) {
|
|
clearTimeout( this.timeoutId );
|
|
this.timeoutId = null;
|
|
}
|
|
|
|
delayPoll = ve.bind( function ( async ) {
|
|
this.timeoutId = setTimeout(
|
|
ve.bind( this.poll, this ),
|
|
async === true ? 0 : this.frequency
|
|
);
|
|
}, this );
|
|
|
|
if ( async === true ) {
|
|
delayPoll( true );
|
|
return;
|
|
}
|
|
|
|
rangySelection = rangy.getSelection();
|
|
range = this.range;
|
|
node = this.node;
|
|
|
|
if (
|
|
rangySelection.anchorNode !== this.rangySelection.anchorNode ||
|
|
rangySelection.anchorOffset !== this.rangySelection.anchorOffset ||
|
|
rangySelection.focusNode !== this.rangySelection.focusNode ||
|
|
rangySelection.focusOffset !== this.rangySelection.focusOffset
|
|
) {
|
|
this.rangySelection.anchorNode = rangySelection.anchorNode;
|
|
this.rangySelection.anchorOffset = rangySelection.anchorOffset;
|
|
this.rangySelection.focusNode = rangySelection.focusNode;
|
|
this.rangySelection.focusOffset = rangySelection.focusOffset;
|
|
|
|
range = new ve.Range(
|
|
ve.ce.getOffset( rangySelection.anchorNode, rangySelection.anchorOffset ),
|
|
ve.ce.getOffset( rangySelection.focusNode, rangySelection.focusOffset )
|
|
);
|
|
|
|
//if ( range.getLength() === 0 ) {
|
|
node = $( rangySelection.anchorNode ).closest( '.ve-ce-branchNode' ).data( 'node' );
|
|
if ( node.canHaveGrandchildren() === true ) {
|
|
node = null;
|
|
}
|
|
/*} else {
|
|
nodes = this.documentView.selectNodes( range, 'branches' );
|
|
if ( nodes.length === 1 && nodes[0].node.canHaveGrandchildren() === false ) {
|
|
node = nodes[0].node;
|
|
} else {
|
|
node = null;
|
|
}
|
|
}*/
|
|
}
|
|
|
|
if ( this.node !== node ) {
|
|
if ( node === null ) {
|
|
this.text = null;
|
|
this.hash = null;
|
|
this.node = null;
|
|
} else {
|
|
this.text = ve.ce.getDomText( node.$[0] );
|
|
this.hash = ve.ce.getDomHash( node.$[0] );
|
|
this.node = node;
|
|
}
|
|
} else {
|
|
if ( node !== null ) {
|
|
text = ve.ce.getDomText( node.$[0] );
|
|
hash = ve.ce.getDomHash( node.$[0] );
|
|
if ( this.text !== text || this.hash !== hash ) {
|
|
this.emit(
|
|
'contentChange',
|
|
node,
|
|
{ 'text': this.text, 'hash': this.hash, 'range': this.range },
|
|
{ 'text': text, 'hash': hash, 'range': range }
|
|
);
|
|
this.text = text;
|
|
this.hash = hash;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Only emit selectionChange event if there's a meaningful range difference
|
|
if ( ( this.range && range ) ? !this.range.equals( range ) : ( this.range !== range ) ) {
|
|
this.emit(
|
|
'selectionChange',
|
|
this.range,
|
|
range
|
|
);
|
|
this.range = range;
|
|
}
|
|
|
|
delayPoll();
|
|
};
|