diff --git a/modules/ve/ce/ve.ce.Surface.js b/modules/ve/ce/ve.ce.Surface.js index 3c554f4162..cbc5ba1352 100644 --- a/modules/ve/ce/ve.ce.Surface.js +++ b/modules/ve/ce/ve.ce.Surface.js @@ -292,7 +292,7 @@ ve.ce.Surface.prototype.documentOnFocus = function () { 'compositionstart.ve-ce-Surface': ve.bind( this.onDocumentCompositionStart, this ), 'compositionend.ve-ce-Surface': ve.bind( this.onDocumentCompositionEnd, this ) } ); - this.surfaceObserver.start( true ); + this.surfaceObserver.start( true, true ); }; /** @@ -303,7 +303,7 @@ ve.ce.Surface.prototype.documentOnFocus = function () { */ ve.ce.Surface.prototype.documentOnBlur = function () { this.$document.off( '.ve-ce-Surface' ); - this.surfaceObserver.stop( true ); + this.surfaceObserver.stop( true, true ); this.dragging = false; }; @@ -323,7 +323,7 @@ ve.ce.Surface.prototype.onDocumentMouseDown = function ( e ) { // this.$$( e.target ).closest( '.ve-ce-documentNode' ).length === 0 if ( e.which === 1 ) { - this.surfaceObserver.stop( true ); + this.surfaceObserver.stop( true, true ); } // Handle triple click @@ -349,7 +349,7 @@ ve.ce.Surface.prototype.onDocumentMouseDown = function ( e ) { * @emits selectionEnd */ ve.ce.Surface.prototype.onDocumentMouseUp = function ( e ) { - this.surfaceObserver.start(); + this.surfaceObserver.start( false, true ); if ( !e.shiftKey && this.selecting ) { this.emit( 'selectionEnd' ); this.selecting = false; @@ -467,7 +467,7 @@ ve.ce.Surface.prototype.onDocumentKeyDown = function ( e ) { return; } - this.surfaceObserver.stop( true ); + this.surfaceObserver.stop( true, false ); switch ( e.keyCode ) { case ve.Keys.LEFT: case ve.Keys.RIGHT: @@ -502,7 +502,7 @@ ve.ce.Surface.prototype.onDocumentKeyDown = function ( e ) { } break; } - this.surfaceObserver.start(); + this.surfaceObserver.start( false, false ); }; /** @@ -540,7 +540,7 @@ ve.ce.Surface.prototype.onDocumentKeyPress = function ( e ) { this.handleInsertion(); setTimeout( ve.bind( function () { - this.surfaceObserver.start(); + this.surfaceObserver.start( false, true ); }, this ) ); }; @@ -566,7 +566,7 @@ ve.ce.Surface.prototype.onDocumentKeyUp = function ( e ) { * @param {jQuery.Event} e Cut event */ ve.ce.Surface.prototype.onCut = function ( e ) { - this.surfaceObserver.stop(); + this.surfaceObserver.stop( false, true ); this.onCopy( e ); setTimeout( ve.bind( function () { var selection, tx; @@ -580,7 +580,7 @@ ve.ce.Surface.prototype.onCut = function ( e ) { this.model.change( tx, new ve.Range( selection.start ) ); this.surfaceObserver.clear(); - this.surfaceObserver.start(); + this.surfaceObserver.start( false, true ); }, this ) ); }; @@ -627,7 +627,7 @@ ve.ce.Surface.prototype.onPaste = function () { view = this, selection = this.model.getSelection(); - this.surfaceObserver.stop(); + this.surfaceObserver.stop( false, true ); // Pasting into a range? Remove first. if ( !rangy.getSelection( this.$document[0] ).isCollapsed ) { @@ -708,7 +708,7 @@ ve.ce.Surface.prototype.onDocumentCompositionStart = function () { */ ve.ce.Surface.prototype.onDocumentCompositionEnd = function () { this.inIme = false; - this.surfaceObserver.start(); + this.surfaceObserver.start( false, false ); }; /*! Custom Events */ @@ -942,7 +942,7 @@ ve.ce.Surface.prototype.onContentChange = function ( node, previous, next ) { * @method */ ve.ce.Surface.prototype.onLock = function () { - this.surfaceObserver.stop(); + this.surfaceObserver.stop( false, true ); }; /** @@ -952,7 +952,7 @@ ve.ce.Surface.prototype.onLock = function () { */ ve.ce.Surface.prototype.onUnlock = function () { this.surfaceObserver.clear( this.model.getSelection() ); - this.surfaceObserver.start(); + this.surfaceObserver.start( false, true ); }; /*! Relocation */ @@ -1000,7 +1000,7 @@ ve.ce.Surface.prototype.handleLeftOrRightArrowKey = function ( e ) { // Selection is going to be displayed programmatically so prevent default browser behaviour e.preventDefault(); // Stop with final poll cycle so we have correct information in model - this.surfaceObserver.stop( true ); + this.surfaceObserver.stop( true, false ); selection = this.model.getSelection(); if ( this.$$( e.target ).css( 'direction' ) === 'rtl' ) { // If the language direction is RTL, switch left/right directions: @@ -1017,7 +1017,7 @@ ve.ce.Surface.prototype.handleLeftOrRightArrowKey = function ( e ) { ); this.model.change( null, range ); - this.surfaceObserver.start(); + this.surfaceObserver.start( false, true ); }; /** @@ -1034,7 +1034,7 @@ ve.ce.Surface.prototype.handleUpOrDownArrowKey = function ( e ) { nativeSel.modify( 'extend', 'left', 'character' ); return; } - this.surfaceObserver.stop( true ); + this.surfaceObserver.stop( true, true ); selection = this.model.getSelection(); rangySelection = rangy.getSelection( this.$document[0] ); // Perform programatic handling only for selection that is expanded and backwards according to @@ -1058,18 +1058,18 @@ ve.ce.Surface.prototype.handleUpOrDownArrowKey = function ( e ) { if ( !$element.hasClass( 've-ce-branchNode-slug' ) ) { $element.remove(); } - this.surfaceObserver.start(); - this.surfaceObserver.stop( false ); + this.surfaceObserver.start( false, true ); + this.surfaceObserver.stop( false, true ); if ( e.shiftKey === true ) { // expanded range range = new ve.Range( selection.from, this.model.getSelection().to ); } else { // collapsed range (just a cursor) range = new ve.Range( this.model.getSelection().to ); } this.model.change( null, range ); - this.surfaceObserver.start(); + this.surfaceObserver.start( false, true ); }, this ), 0 ); } else { - this.surfaceObserver.start(); + this.surfaceObserver.start( false, true ); } }; @@ -1125,7 +1125,7 @@ ve.ce.Surface.prototype.handleInsertion = function () { } } - this.surfaceObserver.stop( true ); + this.surfaceObserver.stop( true, true ); }; /** diff --git a/modules/ve/ce/ve.ce.SurfaceObserver.js b/modules/ve/ce/ve.ce.SurfaceObserver.js index 1124813ba9..e49f406b90 100644 --- a/modules/ve/ce/ve.ce.SurfaceObserver.js +++ b/modules/ve/ce/ve.ce.SurfaceObserver.js @@ -82,15 +82,16 @@ ve.ce.SurfaceObserver.prototype.clear = function ( range ) { /** * Start polling. * - * If {async} is false or undefined the first poll cycle will occur immediately and synchronously. + * If {postpone} is false or undefined the first poll cycle will occur immediately and synchronously. * * @method - * @param {boolean} async Poll the first time asynchronously + * @param {boolean} postpone Add the first poll to the end of the event queue + * @param {boolean} emitContentChanges Allow contentChange to be emitted */ -ve.ce.SurfaceObserver.prototype.start = function ( async ) { +ve.ce.SurfaceObserver.prototype.start = function ( postpone, emitContentChanges ) { this.domDocument = this.documentView.getDocumentNode().getElementDocument(); this.polling = true; - this.poll( async ); + this.poll( postpone, emitContentChanges ); }; /** @@ -101,11 +102,12 @@ ve.ce.SurfaceObserver.prototype.start = function ( async ) { * * @method * @param {boolean} poll Poll one last time before stopping future polling + * @param {boolean} emitContentChanges Allow contentChange to be emitted */ -ve.ce.SurfaceObserver.prototype.stop = function ( poll ) { +ve.ce.SurfaceObserver.prototype.stop = function ( poll, emitContentChanges ) { if ( this.polling === true ) { if ( poll === true ) { - this.poll(); + this.poll( false, emitContentChanges ); } this.polling = false; clearTimeout( this.timeoutId ); @@ -116,7 +118,7 @@ ve.ce.SurfaceObserver.prototype.stop = function ( poll ) { /** * Poll for changes. * - * If `async` is false or undefined then polling will occcur asynchronously. + * If `postpone` is false or undefined then polling will occcur immediately. * * TODO: fixing selection in certain cases, handling selection across multiple nodes in Firefox * @@ -124,12 +126,13 @@ ve.ce.SurfaceObserver.prototype.stop = function ( poll ) { * with a mouse. * * @method - * @param {boolean} async Poll asynchronously + * @param {boolean} postpone Append the poll action to the end of the event queue + * @param {boolean} emitContentChanges Allow contentChange to be emitted * @emits contentChange * @emits selectionChange */ -ve.ce.SurfaceObserver.prototype.poll = function ( async ) { - var delayPoll, $nodeOrSlug, node, text, hash, range, rangyRange; +ve.ce.SurfaceObserver.prototype.poll = function ( postpone, emitContentChanges ) { + var $nodeOrSlug, node, text, hash, range, rangyRange; if ( this.polling === false ) { return; @@ -140,15 +143,13 @@ ve.ce.SurfaceObserver.prototype.poll = function ( async ) { this.timeoutId = null; } - delayPoll = ve.bind( function ( async ) { + if ( postpone === true ) { this.timeoutId = setTimeout( ve.bind( this.poll, this ), - async === true ? 0 : this.frequency + 0, + false, + emitContentChanges ); - }, this ); - - if ( async === true ) { - delayPoll( true ); return; } @@ -178,20 +179,20 @@ ve.ce.SurfaceObserver.prototype.poll = function ( async ) { 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 ) { + } else if ( node !== null ) { + text = ve.ce.getDomText( node.$[0] ); + hash = ve.ce.getDomHash( node.$[0] ); + if ( this.text !== text || this.hash !== hash ) { + if ( emitContentChanges ) { 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; } + this.text = text; + this.hash = hash; } } @@ -205,5 +206,10 @@ ve.ce.SurfaceObserver.prototype.poll = function ( async ) { this.range = range; } - delayPoll(); + this.timeoutId = setTimeout( + ve.bind( this.poll, this ), + this.frequency, + false, + emitContentChanges + ); }; diff --git a/modules/ve/test/ve.test.js b/modules/ve/test/ve.test.js index c87ba9c7ee..a48a1d0100 100644 --- a/modules/ve/test/ve.test.js +++ b/modules/ve/test/ve.test.js @@ -454,18 +454,18 @@ QUnit.test( 'createDocumentFromHtml', function ( assert ) { // TODO: ve.getCharacterOffset QUnit.test( 'graphemeSafeSubstring', function ( assert ) { - var i, text = '12𨋢45𨋢789𨋢bc', cases = [ + var i, text = '12\ud860\udee245\ud860\udee2789\ud860\udee2bc', cases = [ { 'msg': 'start and end inside multibyte', 'start': 3, 'end': 12, - 'expected': [ '𨋢45𨋢789𨋢', '45𨋢789' ] + 'expected': [ '\ud860\udee245\ud860\udee2789\ud860\udee2', '45\ud860\udee2789' ] }, { 'msg': 'start and end next to multibyte', 'start': 4, 'end': 11, - 'expected': [ '45𨋢789', '45𨋢789' ] + 'expected': [ '45\ud860\udee2789', '45\ud860\udee2789' ] }, { 'msg': 'complete string', @@ -477,7 +477,7 @@ QUnit.test( 'graphemeSafeSubstring', function ( assert ) { 'msg': 'collapsed selection inside multibyte', 'start': 3, 'end': 3, - 'expected': [ '𨋢', '' ] + 'expected': [ '\ud860\udee2', '' ] } ]; QUnit.expect( cases.length * 2 ); diff --git a/modules/ve/ui/ve.ui.Surface.js b/modules/ve/ui/ve.ui.Surface.js index e31ab14407..1510eff8be 100644 --- a/modules/ve/ui/ve.ui.Surface.js +++ b/modules/ve/ui/ve.ui.Surface.js @@ -82,7 +82,7 @@ ve.ui.Surface.prototype.initialize = function () { // By re-asserting the current selection and forcing a poll we force selection to be something // reasonable - otherwise in Firefox, the initial selection is (0,0), causing bug 42277 this.model.getFragment().select(); - this.view.surfaceObserver.poll(); + this.view.surfaceObserver.poll( false, true ); this.model.startHistoryTracking(); };