Index: trunk/extensions/VisualEditor/modules/es/models/es.SurfaceModel.js |
— | — | @@ -13,6 +13,7 @@ |
14 | 14 | // Properties |
15 | 15 | this.doc = doc; |
16 | 16 | this.selection = new es.Range(); |
| 17 | + this.previousSelection = null; |
17 | 18 | this.states = [[]]; |
18 | 19 | this.initializeState( this.states.length - 1 ); |
19 | 20 | |
— | — | @@ -67,15 +68,25 @@ |
68 | 69 | if ( !combine && this.shouldPushState( selection ) ) { |
69 | 70 | this.pushState(); |
70 | 71 | } |
71 | | - var lastAction = this.states[this.states.length - 1]; |
72 | | - if ( lastAction instanceof es.Range ) { |
73 | | - this.currentStateDistance += Math.abs( |
74 | | - selection.from - this.states[this.states.length - 1].from |
75 | | - ); |
| 72 | + // Filter out calls to select if they do not change the selection values |
| 73 | + this.selection = selection; |
| 74 | + if ( |
| 75 | + !combine || |
| 76 | + !this.previousSelection || ( |
| 77 | + this.previousSelection.from !== this.selection.from || |
| 78 | + this.previousSelection.to !== this.selection.to |
| 79 | + ) |
| 80 | + ) { |
| 81 | + var lastAction = this.states[this.states.length - 1]; |
| 82 | + if ( lastAction instanceof es.Range ) { |
| 83 | + this.currentStateDistance += Math.abs( |
| 84 | + selection.from - this.states[this.states.length - 1].from |
| 85 | + ); |
| 86 | + } |
| 87 | + this.currentState.push( selection ); |
| 88 | + this.emit( 'select', this.selection.clone() ); |
76 | 89 | } |
77 | | - this.selection = selection; |
78 | | - this.currentState.push( selection ); |
79 | | - this.emit( 'select', selection ); |
| 90 | + this.previousSelection = this.selection.clone(); |
80 | 91 | }; |
81 | 92 | |
82 | 93 | /** |
— | — | @@ -199,11 +210,25 @@ |
200 | 211 | } |
201 | 212 | } |
202 | 213 | // Push a new state to the stack |
| 214 | + this.optimizeState( this.states.length - 1 ); |
203 | 215 | this.states.push( [] ); |
204 | 216 | this.initializeState( this.states.length - 1 ); |
205 | 217 | this.emit( 'pushState' ); |
206 | 218 | }; |
207 | 219 | |
| 220 | +es.SurfaceModel.prototype.optimizeState = function( stateIndex ) { |
| 221 | + var skipSelects = false, |
| 222 | + newState = []; |
| 223 | + for ( var i = this.states[stateIndex].length - 1; i >= 0; i-- ) { |
| 224 | + var action = this.states[stateIndex][i]; |
| 225 | + if ( !( action instanceof es.Range && skipSelects ) ) { |
| 226 | + newState.push( action ); |
| 227 | + skipSelects = true; |
| 228 | + } |
| 229 | + } |
| 230 | + this.states[stateIndex] = newState; |
| 231 | +}; |
| 232 | + |
208 | 233 | /* Inheritance */ |
209 | 234 | |
210 | 235 | es.extendClass( es.SurfaceModel, es.EventEmitter ); |
Index: trunk/extensions/VisualEditor/modules/es/views/es.SurfaceView.js |
— | — | @@ -7,86 +7,86 @@ |
8 | 8 | * @param {es.SurfaceModel} model Surface model to view |
9 | 9 | */ |
10 | 10 | es.SurfaceView = function( $container, model ) { |
11 | | - // References for use in closures |
12 | | - var _this = this, |
13 | | - $document = $( document ); |
14 | | - |
| 11 | + // Inheritance |
15 | 12 | es.EventEmitter.call( this ); |
16 | 13 | |
17 | | - this.$ = $container.addClass( 'es-surfaceView' ); |
18 | | - this.$window = $( window ); |
| 14 | + // References for use in closures |
| 15 | + var _this = this, |
| 16 | + $document = $( document ), |
| 17 | + $window = $( window ); |
| 18 | + |
| 19 | + // Properties |
19 | 20 | this.model = model; |
20 | | - this.selection = new es.Range(); |
21 | | - |
22 | | - // Mac uses different mapping for keyboard shortcuts |
23 | | - this.mac = navigator.userAgent.match(/mac/i) ? true : false; |
24 | | - |
25 | | - this.model.getDocument().on( 'update', function() { |
26 | | - _this.emit( 'update' ); |
27 | | - } ); |
28 | | - |
29 | | - this.previousSelection = null; |
30 | | - this.emitSelect = function() { |
31 | | - if ( _this.previousSelection ) { |
32 | | - if ( |
33 | | - _this.previousSelection.from !== _this.selection.from || |
34 | | - _this.previousSelection.to !== _this.selection.to |
35 | | - ) { |
36 | | - _this.emit( 'select', _this.selection.clone() ); |
37 | | - _this.previousSelection = _this.selection.clone(); |
38 | | - } |
39 | | - // Mouse movement that doesn't change selection points will terminate here |
40 | | - } else { |
41 | | - _this.previousSelection = _this.selection.clone(); |
42 | | - } |
43 | | - }; |
44 | | - |
45 | | - // Initialize document view |
| 21 | + this.currentSelection = new es.Range(); |
46 | 22 | this.documentView = new es.DocumentView( this.model.getDocument(), this ); |
47 | | - this.$.append( this.documentView.$ ); |
| 23 | + this.$ = $container |
| 24 | + .addClass( 'es-surfaceView' ) |
| 25 | + .append( this.documentView.$ ); |
| 26 | + this.$input = $( '<textarea class="es-surfaceView-textarea" />' ) |
| 27 | + .prependTo( this.$ ); |
| 28 | + this.$cursor = $( '<div class="es-surfaceView-cursor"></div>' ) |
| 29 | + .appendTo( this.$ ); |
48 | 30 | |
49 | | - // Interaction state |
| 31 | + // Interaction states |
50 | 32 | |
51 | | - // There are three different selection modes available for mouse. Selection of: |
52 | | - // * 1 - chars |
53 | | - // * 2 - words |
54 | | - // * 3 - nodes (e.g. paragraph, listitem) |
55 | | - // |
56 | | - // In case of 2 and 3 selectedRange stores the range of original selection caused by double |
57 | | - // or triple mousedowns. |
| 33 | + /* |
| 34 | + * There are three different selection modes available for mouse. Selection of: |
| 35 | + * 1 - chars |
| 36 | + * 2 - words |
| 37 | + * 3 - nodes (e.g. paragraph, listitem) |
| 38 | + * |
| 39 | + * In case of 2 and 3 selectedRange stores the range of original selection caused by double |
| 40 | + * or triple mousedowns. |
| 41 | + */ |
58 | 42 | this.mouse = { |
59 | 43 | selectingMode: null, |
60 | 44 | selectedRange: null |
61 | 45 | }; |
62 | | - |
63 | 46 | this.cursor = { |
64 | | - $: $( '<div class="es-surfaceView-cursor"></div>' ).appendTo( this.$ ), |
65 | 47 | interval: null, |
66 | 48 | initialLeft: null, |
67 | 49 | initialBias: false |
68 | 50 | }; |
69 | | - |
70 | 51 | this.keyboard = { |
71 | 52 | selecting: false, |
72 | 53 | cursorAnchor: null, |
73 | 54 | keydownTimeout: null, |
74 | 55 | keys: { shift: false } |
75 | 56 | }; |
| 57 | + this.dimensions = { |
| 58 | + width: this.$.width(), |
| 59 | + height: $window.height(), |
| 60 | + scrollTop: $window.scrollTop(), |
| 61 | + // XXX: This is a dirty hack! |
| 62 | + toolbarTop: $( '#es-toolbar' ).offset().top, |
| 63 | + toolbarHeight: $( '#es-toolbar' ).height() |
| 64 | + }; |
76 | 65 | |
77 | | - // MouseDown and DoubleClick on surface |
78 | | - this.$.on( { |
79 | | - 'mousedown' : function(e) { |
80 | | - return _this.onMouseDown( e ); |
| 66 | + // Events |
| 67 | + |
| 68 | + this.model.on( 'select', function( selection ) { |
| 69 | + // Keep a copy of the current selection on hand |
| 70 | + _this.currentSelection = selection.clone(); |
| 71 | + // Respond to selection changes |
| 72 | + if ( selection.from !== selection.to ) { |
| 73 | + _this.hideCursor(); |
| 74 | + } else { |
| 75 | + _this.showCursor(); |
81 | 76 | } |
| 77 | + _this.documentView.drawSelection( selection ); |
82 | 78 | } ); |
83 | | - |
84 | | - // Hidden input |
85 | | - this.$input = $( '<textarea class="es-surfaceView-textarea" />' ) |
86 | | - .prependTo( this.$ ) |
87 | | - .on( { |
88 | | - 'focus' : function() { |
| 79 | + this.model.getDocument().on( 'update', function() { |
| 80 | + _this.emit( 'update' ); |
| 81 | + } ); |
| 82 | + this.$.mousedown( function(e) { |
| 83 | + return _this.onMouseDown( e ); |
| 84 | + } ); |
| 85 | + this.$input.on( { |
| 86 | + 'focus': function() { |
| 87 | + // Make sure we aren't double-binding |
89 | 88 | $document.off( '.es-surfaceView' ); |
90 | | - $document.on({ |
| 89 | + // Bind mouse and key events to the document to ensure we don't miss anything |
| 90 | + $document.on( { |
91 | 91 | 'mousemove.es-surfaceView': function(e) { |
92 | 92 | return _this.onMouseMove( e ); |
93 | 93 | }, |
— | — | @@ -99,40 +99,29 @@ |
100 | 100 | 'keyup.es-surfaceView': function( e ) { |
101 | 101 | return _this.onKeyUp( e ); |
102 | 102 | } |
103 | | - }); |
| 103 | + } ); |
104 | 104 | }, |
105 | 105 | 'blur': function( e ) { |
| 106 | + // Release our event handlers when not focused |
106 | 107 | $document.off( '.es-surfaceView' ); |
107 | 108 | _this.hideCursor(); |
108 | 109 | } |
109 | | - } ).focus(); |
110 | | - |
111 | | - // First render |
112 | | - this.documentView.renderContent(); |
113 | | - |
114 | | - this.dimensions = { |
115 | | - width: this.$.width(), |
116 | | - height: this.$window.height(), |
117 | | - scrollTop: this.$window.scrollTop(), |
118 | | - toolbarTop: $( '#es-toolbar' ).offset().top, |
119 | | - toolbarHeight: $( '#es-toolbar' ).height() |
120 | | - }; |
121 | | - |
122 | | - // Re-render when resizing horizontally |
123 | | - // TODO: Instead of re-rendering on every single 'resize' event wait till user is done with |
124 | | - // resizing - can be implemented with setTimeout |
125 | | - this.$window.resize( function() { |
| 110 | + } ); |
| 111 | + $window.resize( function() { |
| 112 | + // Re-render when resizing horizontally |
| 113 | + // TODO: Instead of re-rendering on every single 'resize' event wait till user is done with |
| 114 | + // resizing - can be implemented with setTimeout |
126 | 115 | _this.hideCursor(); |
127 | | - _this.dimensions.height = _this.$window.height(); |
| 116 | + _this.dimensions.height = $window.height(); |
128 | 117 | var width = _this.$.width(); |
129 | 118 | if ( _this.dimensions.width !== width ) { |
130 | 119 | _this.dimensions.width = width; |
131 | 120 | _this.documentView.renderContent(); |
132 | 121 | } |
133 | 122 | } ); |
134 | | - |
135 | | - this.$window.scroll( function() { |
136 | | - _this.dimensions.scrollTop = _this.$window.scrollTop(); |
| 123 | + $window.scroll( function() { |
| 124 | + // FIXME: Is this code in the right place? |
| 125 | + _this.dimensions.scrollTop = $window.scrollTop(); |
137 | 126 | if ( _this.dimensions.scrollTop >= _this.dimensions.toolbarTop ) { |
138 | 127 | $( '#es-toolbar' ).addClass( 'float' ); |
139 | 128 | $( '#es-panes' ).css( 'padding-top', _this.dimensions.toolbarHeight ); |
— | — | @@ -141,147 +130,155 @@ |
142 | 131 | $( '#es-panes' ).css( 'padding-top', 0 ); |
143 | 132 | } |
144 | 133 | } ); |
| 134 | + |
| 135 | + // Configuration |
| 136 | + this.mac = navigator.userAgent.match(/mac/i) ? true : false; // (yes it's evil, for keys only!) |
| 137 | + |
| 138 | + // Initialization |
| 139 | + this.$input.focus(); |
| 140 | + this.documentView.renderContent(); |
145 | 141 | }; |
146 | 142 | |
147 | 143 | /* Methods */ |
148 | 144 | |
149 | 145 | es.SurfaceView.prototype.onMouseDown = function( e ) { |
150 | | - if ( e.button === 0 ) { // left mouse button |
| 146 | + // Only for left mouse button |
| 147 | + if ( e.button === 0 ) { |
| 148 | + var selection = this.currentSelection.clone(), |
| 149 | + offset = this.documentView.getOffsetFromEvent( e ); |
151 | 150 | |
152 | | - this.selection.normalize(); |
| 151 | + // Single click |
| 152 | + if ( e.originalEvent.detail === 1 ) { |
| 153 | + // @see {es.SurfaceView.prototype.onMouseMove} |
| 154 | + this.mouse.selectingMode = 1; |
153 | 155 | |
154 | | - var offset = this.documentView.getOffsetFromEvent( e ); |
155 | | - |
156 | | - if ( e.originalEvent.detail === 1 ) { // single click |
157 | | - this.mouse.selectingMode = 1; // used in mouseMove handler |
158 | | - |
159 | | - if ( this.keyboard.keys.shift && offset !== this.selection.from ) { |
160 | | - // extend current or create new selection |
161 | | - this.selection.to = offset; |
| 156 | + if ( this.keyboard.keys.shift && offset !== selection.from ) { |
| 157 | + // Extend current or create new selection |
| 158 | + selection.to = offset; |
162 | 159 | } else { |
163 | | - if ( this.selection.to !== this.selection.from ) { |
164 | | - // clear the selection if there was any |
| 160 | + if ( selection.to !== selection.from ) { |
| 161 | + // Clear the selection if there was any |
165 | 162 | this.documentView.clearSelection(); |
166 | 163 | } |
167 | | - this.selection.from = this.selection.to = offset; |
| 164 | + selection.from = selection.to = offset; |
168 | 165 | |
169 | 166 | var position = es.Position.newFromEventPagePosition( e ), |
170 | 167 | nodeView = this.documentView.getNodeFromOffset( offset, false ); |
171 | 168 | this.cursor.initialBias = position.left > nodeView.contentView.$.offset().left; |
172 | 169 | } |
173 | 170 | |
174 | | - } else if ( e.originalEvent.detail === 2 ) { // double click |
175 | | - this.mouse.selectingMode = 2; // used in mouseMove handler |
| 171 | + } |
| 172 | + // Double click |
| 173 | + else if ( e.originalEvent.detail === 2 ) { |
| 174 | + // @see {es.SurfaceView.prototype.onMouseMove} |
| 175 | + this.mouse.selectingMode = 2; |
176 | 176 | |
177 | 177 | var wordRange = this.model.getDocument().getWordBoundaries( offset ); |
178 | 178 | if( wordRange ) { |
179 | | - this.selection = wordRange; |
180 | | - this.mouse.selectedRange = this.selection.clone(); |
| 179 | + selection = wordRange; |
| 180 | + this.mouse.selectedRange = selection.clone(); |
181 | 181 | } |
182 | 182 | |
183 | | - } else if ( e.originalEvent.detail >= 3 ) { // triple click |
184 | | - this.mouse.selectingMode = 3; // used in mouseMove handler |
| 183 | + } |
| 184 | + // Triple click |
| 185 | + else if ( e.originalEvent.detail >= 3 ) { |
| 186 | + // @see {es.SurfaceView.prototype.onMouseMove} |
| 187 | + this.mouse.selectingMode = 3; |
185 | 188 | |
186 | 189 | var node = this.documentView.getNodeFromOffset( offset ), |
187 | 190 | nodeOffset = this.documentView.getOffsetFromNode( node, false ); |
188 | 191 | |
189 | | - this.selection.from = this.model.getDocument().getRelativeContentOffset( |
190 | | - nodeOffset, |
191 | | - 1 |
| 192 | + selection.from = this.model.getDocument().getRelativeContentOffset( nodeOffset, 1 ); |
| 193 | + selection.to = this.model.getDocument().getRelativeContentOffset( |
| 194 | + nodeOffset + node.getElementLength(), -1 |
192 | 195 | ); |
193 | | - this.selection.to = this.model.getDocument().getRelativeContentOffset( |
194 | | - nodeOffset + node.getElementLength(), |
195 | | - -1 |
196 | | - ); |
197 | | - this.mouse.selectedRange = this.selection.clone(); |
| 196 | + this.mouse.selectedRange = selection.clone(); |
198 | 197 | } |
199 | 198 | |
200 | | - if ( this.selection.from === this.selection.to ) { |
201 | | - this.showCursor(); |
202 | | - } else { |
203 | | - this.hideCursor(); |
204 | | - this.documentView.drawSelection( this.selection ); |
205 | | - } |
| 199 | + // Reset the initial left position |
| 200 | + this.cursor.initialLeft = null; |
| 201 | + // Apply new selection |
| 202 | + this.model.select( selection ); |
206 | 203 | } |
207 | | - |
| 204 | + // If the inut isn't already focused, focus it and select it's contents |
208 | 205 | if ( !this.$input.is( ':focus' ) ) { |
209 | 206 | this.$input.focus().select(); |
210 | 207 | } |
211 | | - this.cursor.initialLeft = null; |
212 | | - this.emitSelect(); |
213 | 208 | return false; |
214 | 209 | }; |
215 | 210 | |
216 | 211 | es.SurfaceView.prototype.onMouseMove = function( e ) { |
217 | | - if ( e.button === 0 && this.mouse.selectingMode ) { // left mouse button and in selecting mode |
| 212 | + // Only with the left mouse button while in selecting mode |
| 213 | + if ( e.button === 0 && this.mouse.selectingMode ) { |
| 214 | + var selection = this.currentSelection.clone(), |
| 215 | + offset = this.documentView.getOffsetFromEvent( e ); |
218 | 216 | |
219 | | - var offset = this.documentView.getOffsetFromEvent( e ); |
220 | | - |
221 | | - if ( this.mouse.selectingMode === 1 ) { // selection of chars |
222 | | - this.selection.to = offset; |
223 | | - } else if ( this.mouse.selectingMode === 2 ) { // selection of words |
| 217 | + // Character selection |
| 218 | + if ( this.mouse.selectingMode === 1 ) { |
| 219 | + selection.to = offset; |
| 220 | + } |
| 221 | + // Word selection |
| 222 | + else if ( this.mouse.selectingMode === 2 ) { |
224 | 223 | var wordRange = this.model.getDocument().getWordBoundaries( offset ); |
225 | 224 | if ( wordRange ) { |
226 | 225 | if ( wordRange.to <= this.mouse.selectedRange.from ) { |
227 | | - this.selection.from = wordRange.from; |
228 | | - this.selection.to = this.mouse.selectedRange.to; |
| 226 | + selection.from = wordRange.from; |
| 227 | + selection.to = this.mouse.selectedRange.to; |
229 | 228 | } else { |
230 | | - this.selection.from = this.mouse.selectedRange.from; |
231 | | - this.selection.to = wordRange.to; |
| 229 | + selection.from = this.mouse.selectedRange.from; |
| 230 | + selection.to = wordRange.to; |
232 | 231 | } |
233 | 232 | } |
234 | | - } else if ( this.mouse.selectingMode === 3 ) { |
| 233 | + } |
| 234 | + // Node selection |
| 235 | + else if ( this.mouse.selectingMode === 3 ) { |
| 236 | + // @see {es.SurfaceView.prototype.onMouseMove} |
| 237 | + this.mouse.selectingMode = 3; |
235 | 238 | |
236 | | - this.mouse.selectingMode = 3; // used in mouseMove handler |
237 | | - |
238 | 239 | var nodeRange = this.documentView.getRangeFromNode( |
239 | 240 | this.documentView.getNodeFromOffset( offset ) |
240 | 241 | ); |
241 | 242 | if ( nodeRange.to <= this.mouse.selectedRange.from ) { |
242 | | - this.selection.from = this.model.getDocument().getRelativeContentOffset( |
243 | | - nodeRange.from, |
244 | | - 1 |
| 243 | + selection.from = this.model.getDocument().getRelativeContentOffset( |
| 244 | + nodeRange.from, 1 |
245 | 245 | ); |
246 | | - this.selection.to = this.mouse.selectedRange.to; |
| 246 | + selection.to = this.mouse.selectedRange.to; |
247 | 247 | } else { |
248 | | - this.selection.from = this.mouse.selectedRange.from; |
249 | | - this.selection.to = this.model.getDocument().getRelativeContentOffset( |
250 | | - nodeRange.to, |
251 | | - -1 |
| 248 | + selection.from = this.mouse.selectedRange.from; |
| 249 | + selection.to = this.model.getDocument().getRelativeContentOffset( |
| 250 | + nodeRange.to, -1 |
252 | 251 | ); |
253 | 252 | } |
254 | 253 | } |
255 | | - |
256 | | - this.emitSelect(); |
257 | | - |
258 | | - this.documentView.drawSelection( this.selection ); |
259 | | - if ( this.selection.from !== this.selection.to ) { |
260 | | - this.hideCursor(); |
261 | | - } |
| 254 | + // Apply new selection |
| 255 | + this.model.select( selection, true ); |
262 | 256 | } |
263 | 257 | }; |
264 | 258 | |
265 | 259 | es.SurfaceView.prototype.onMouseUp = function( e ) { |
266 | 260 | if ( e.button === 0 ) { // left mouse button |
267 | 261 | this.mouse.selectingMode = this.mouse.selectedRange = null; |
| 262 | + this.model.select( this.currentSelection ); |
268 | 263 | } |
269 | 264 | }; |
270 | 265 | |
271 | 266 | es.SurfaceView.prototype.onKeyDown = function( e ) { |
272 | | - this.selection.normalize(); |
273 | | - |
274 | 267 | switch ( e.keyCode ) { |
275 | | - case 16: // Shift |
| 268 | + // Shift |
| 269 | + case 16: |
276 | 270 | this.keyboard.keys.shift = true; |
277 | 271 | this.keyboard.selecting = true; |
278 | 272 | break; |
279 | | - case 36: // Home |
| 273 | + // Home |
| 274 | + case 36: |
280 | 275 | this.moveCursor( 'left', 'line' ); |
281 | 276 | break; |
282 | | - case 35: // End |
| 277 | + // End |
| 278 | + case 35: |
283 | 279 | this.moveCursor( 'right', 'line' ); |
284 | 280 | break; |
285 | | - case 37: // Left arrow |
| 281 | + // Left arrow |
| 282 | + case 37: |
286 | 283 | if ( !this.mac ) { |
287 | 284 | if ( e.ctrlKey ) { |
288 | 285 | this.moveCursor( 'left', 'word' ); |
— | — | @@ -298,7 +295,8 @@ |
299 | 296 | } |
300 | 297 | } |
301 | 298 | break; |
302 | | - case 38: // Up arrow |
| 299 | + // Up arrow |
| 300 | + case 38: |
303 | 301 | if ( !this.mac ) { |
304 | 302 | if ( e.ctrlKey ) { |
305 | 303 | this.moveCursor( 'up', 'unit' ); |
— | — | @@ -313,7 +311,8 @@ |
314 | 312 | } |
315 | 313 | } |
316 | 314 | break; |
317 | | - case 39: // Right arrow |
| 315 | + // Right arrow |
| 316 | + case 39: |
318 | 317 | if ( !this.mac ) { |
319 | 318 | if ( e.ctrlKey ) { |
320 | 319 | this.moveCursor( 'right', 'word' ); |
— | — | @@ -330,7 +329,8 @@ |
331 | 330 | } |
332 | 331 | } |
333 | 332 | break; |
334 | | - case 40: // Down arrow |
| 333 | + // Down arrow |
| 334 | + case 40: |
335 | 335 | if ( !this.mac ) { |
336 | 336 | if ( e.ctrlKey ) { |
337 | 337 | this.moveCursor( 'down', 'unit' ); |
— | — | @@ -345,34 +345,31 @@ |
346 | 346 | } |
347 | 347 | } |
348 | 348 | break; |
349 | | - case 8: // Backspace |
| 349 | + // Backspace |
| 350 | + case 8: |
350 | 351 | this.handleDelete( true ); |
351 | 352 | break; |
352 | | - case 46: // Delete |
| 353 | + // Delete |
| 354 | + case 46: |
353 | 355 | this.handleDelete(); |
354 | 356 | break; |
355 | | - case 13: // Enter |
| 357 | + // Enter |
| 358 | + case 13: |
356 | 359 | this.handleEnter(); |
357 | 360 | e.preventDefault(); |
358 | 361 | break; |
359 | | - /* |
360 | | - case 90: // z (undo/redo) |
| 362 | + // Z (undo/redo) |
| 363 | + case 90: |
361 | 364 | if ( e.metaKey || e.ctrlKey ) { |
362 | 365 | if ( this.keyboard.keys.shift ) { |
363 | | - this.history.redo(); |
| 366 | + this.model.redo(); |
364 | 367 | } else { |
365 | | - this.history.undo(); |
| 368 | + this.model.undo(); |
366 | 369 | } |
367 | | - var selection = this.history.getCurrentStateSelection(); |
368 | | - if ( selection ) { |
369 | | - this.selection = selection.clone(); |
370 | | - this.showCursor(); |
371 | | - } |
372 | 370 | break; |
373 | 371 | } |
374 | | - // Fall through to default so the z key still otherwise works |
375 | | - */ |
376 | | - default: // Insert content (maybe) |
| 372 | + // Insert content (maybe) |
| 373 | + default: |
377 | 374 | if ( this.keyboard.keydownTimeout ) { |
378 | 375 | clearTimeout( this.keyboard.keydownTimeout ); |
379 | 376 | } |
— | — | @@ -400,24 +397,25 @@ |
401 | 398 | }; |
402 | 399 | |
403 | 400 | es.SurfaceView.prototype.handleDelete = function( backspace ) { |
404 | | - var sourceOffset, |
| 401 | + var selection = this.currentSelection.clone(), |
| 402 | + sourceOffset, |
405 | 403 | targetOffset, |
406 | 404 | sourceSplitableNode, |
407 | 405 | targetSplitableNode, |
408 | 406 | tx; |
409 | | - if ( this.selection.from === this.selection.to ) { |
| 407 | + if ( selection.from === selection.to ) { |
410 | 408 | if ( backspace ) { |
411 | | - sourceOffset = this.selection.to; |
| 409 | + sourceOffset = selection.to; |
412 | 410 | targetOffset = this.model.getDocument().getRelativeContentOffset( |
413 | 411 | sourceOffset, |
414 | 412 | -1 |
415 | 413 | ); |
416 | 414 | } else { |
417 | 415 | sourceOffset = this.model.getDocument().getRelativeContentOffset( |
418 | | - this.selection.to, |
| 416 | + selection.to, |
419 | 417 | 1 |
420 | 418 | ); |
421 | | - targetOffset = this.selection.to; |
| 419 | + targetOffset = selection.to; |
422 | 420 | } |
423 | 421 | |
424 | 422 | var sourceNode = this.documentView.getNodeFromOffset( sourceOffset, false ), |
— | — | @@ -428,8 +426,8 @@ |
429 | 427 | targetSplitableNode = es.DocumentViewNode.getSplitableNode( targetNode ); |
430 | 428 | } |
431 | 429 | |
432 | | - this.selection.from = this.selection.to = targetOffset; |
433 | | - this.showCursor(); |
| 430 | + selection.from = selection.to = targetOffset; |
| 431 | + this.model.select( selection ); |
434 | 432 | |
435 | 433 | if ( sourceNode === targetNode || |
436 | 434 | ( typeof sourceSplitableNode !== 'undefined' && |
— | — | @@ -460,32 +458,33 @@ |
461 | 459 | } |
462 | 460 | } else { |
463 | 461 | // selection removal |
464 | | - tx = this.model.getDocument().prepareRemoval( this.selection ); |
| 462 | + tx = this.model.getDocument().prepareRemoval( selection ); |
465 | 463 | this.model.transact( tx ); |
466 | 464 | this.documentView.clearSelection(); |
467 | | - this.selection.from = this.selection.to = this.selection.start; |
468 | | - this.showCursor(); |
| 465 | + selection.from = selection.to = selection.start; |
| 466 | + this.model.select( selection ); |
469 | 467 | } |
470 | 468 | }; |
471 | 469 | |
472 | 470 | es.SurfaceView.prototype.handleEnter = function() { |
473 | | - if ( this.selection.from !== this.selection.to ) { |
| 471 | + var selection = this.currentSelection.clone(), |
| 472 | + tx; |
| 473 | + if ( selection.from !== selection.to ) { |
474 | 474 | this.handleDelete(); |
475 | 475 | } |
476 | | - var node = this.documentView.getNodeFromOffset( this.selection.to, false ), |
| 476 | + var node = this.documentView.getNodeFromOffset( selection.to, false ), |
477 | 477 | nodeOffset = this.documentView.getOffsetFromNode( node, false ); |
478 | 478 | |
479 | 479 | if ( |
480 | | - nodeOffset + node.getContentLength() + 1 === this.selection.to && |
| 480 | + nodeOffset + node.getContentLength() + 1 === selection.to && |
481 | 481 | node === es.DocumentViewNode.getSplitableNode( node ) |
482 | 482 | ) { |
483 | | - var tx = this.documentView.model.prepareInsertion( |
| 483 | + tx = this.documentView.model.prepareInsertion( |
484 | 484 | nodeOffset + node.getElementLength(), |
485 | 485 | [ { 'type': 'paragraph' }, { 'type': '/paragraph' } ] |
486 | 486 | ); |
487 | 487 | this.model.transact( tx ); |
488 | | - this.selection.from = this.selection.to = nodeOffset + node.getElementLength() + 1; |
489 | | - this.showCursor(); |
| 488 | + selection.from = selection.to = nodeOffset + node.getElementLength() + 1; |
490 | 489 | } else { |
491 | 490 | var stack = [], |
492 | 491 | splitable = false; |
— | — | @@ -509,31 +508,32 @@ |
510 | 509 | ); |
511 | 510 | splitable = es.DocumentView.splitRules[ elementType ].self; |
512 | 511 | } ); |
513 | | - var tx = this.documentView.model.prepareInsertion( this.selection.to, stack ); |
| 512 | + tx = this.documentView.model.prepareInsertion( selection.to, stack ); |
514 | 513 | this.model.transact( tx ); |
515 | | - this.selection.from = this.selection.to = |
516 | | - this.model.getDocument().getRelativeContentOffset( this.selection.to, 1 ); |
517 | | - this.showCursor(); |
| 514 | + selection.from = selection.to = |
| 515 | + this.model.getDocument().getRelativeContentOffset( selection.to, 1 ); |
518 | 516 | } |
| 517 | + this.model.select( selection ); |
519 | 518 | }; |
520 | 519 | |
521 | 520 | es.SurfaceView.prototype.insertFromInput = function() { |
522 | | - var val = this.$input.val(); |
| 521 | + var selection = this.currentSelection.clone(), |
| 522 | + val = this.$input.val(); |
523 | 523 | this.$input.val( '' ); |
524 | 524 | if ( val.length > 0 ) { |
525 | 525 | var tx; |
526 | | - if ( this.selection.from != this.selection.to ) { |
527 | | - tx = this.model.getDocument().prepareRemoval( this.selection ); |
| 526 | + if ( selection.from != selection.to ) { |
| 527 | + tx = this.model.getDocument().prepareRemoval( selection ); |
528 | 528 | this.model.transact( tx ); |
529 | 529 | this.documentView.clearSelection(); |
530 | | - this.selection.from = this.selection.to = |
531 | | - Math.min( this.selection.from, this.selection.to ); |
| 530 | + selection.from = selection.to = |
| 531 | + Math.min( selection.from, selection.to ); |
532 | 532 | } |
533 | | - tx = this.model.getDocument().prepareInsertion( this.selection.from, val.split('') ); |
| 533 | + tx = this.model.getDocument().prepareInsertion( selection.from, val.split('') ); |
534 | 534 | this.model.transact( tx ); |
535 | | - this.selection.from += val.length; |
536 | | - this.selection.to += val.length; |
537 | | - this.showCursor(); |
| 535 | + selection.from += val.length; |
| 536 | + selection.to += val.length; |
| 537 | + this.model.select( selection ); |
538 | 538 | } |
539 | 539 | }; |
540 | 540 | |
— | — | @@ -545,20 +545,19 @@ |
546 | 546 | if ( direction !== 'up' && direction !== 'down' ) { |
547 | 547 | this.cursor.initialLeft = null; |
548 | 548 | } |
549 | | - |
550 | | - var to, |
| 549 | + var selection = this.currentSelection.clone(), |
| 550 | + to, |
551 | 551 | offset; |
552 | | - |
553 | 552 | switch ( direction ) { |
554 | 553 | case 'left': |
555 | 554 | case 'right': |
556 | 555 | switch ( unit ) { |
557 | 556 | case 'char': |
558 | 557 | case 'word': |
559 | | - if ( this.keyboard.keys.shift || this.selection.from === this.selection.to ) { |
560 | | - offset = this.selection.to; |
| 558 | + if ( this.keyboard.keys.shift || selection.from === selection.to ) { |
| 559 | + offset = selection.to; |
561 | 560 | } else { |
562 | | - offset = direction === 'left' ? this.selection.start : this.selection.end; |
| 561 | + offset = direction === 'left' ? selection.start : selection.end; |
563 | 562 | } |
564 | 563 | to = this.model.getDocument().getRelativeContentOffset( |
565 | 564 | offset, |
— | — | @@ -576,9 +575,9 @@ |
577 | 576 | case 'line': |
578 | 577 | offset = this.cursor.initialBias ? |
579 | 578 | this.model.getDocument().getRelativeContentOffset( |
580 | | - this.selection.to, |
| 579 | + selection.to, |
581 | 580 | -1) : |
582 | | - this.selection.to; |
| 581 | + selection.to; |
583 | 582 | var range = this.documentView.getRenderedLineRangeFromOffset( offset ); |
584 | 583 | to = direction === 'left' ? range.start : range.end; |
585 | 584 | break; |
— | — | @@ -598,7 +597,7 @@ |
599 | 598 | return false; |
600 | 599 | } |
601 | 600 | }, |
602 | | - this.documentView.getNodeFromOffset( this.selection.to, false ).getModel(), |
| 601 | + this.documentView.getNodeFromOffset( selection.to, false ).getModel(), |
603 | 602 | direction === 'up' ? true : false |
604 | 603 | ); |
605 | 604 | to = this.model.getDocument().getOffsetFromNode( toNode, false ) + 1; |
— | — | @@ -610,7 +609,7 @@ |
611 | 610 | * reach the next/previous line |
612 | 611 | */ |
613 | 612 | var position = this.documentView.getRenderedPositionFromOffset( |
614 | | - this.selection.to, |
| 613 | + selection.to, |
615 | 614 | this.cursor.initialBias |
616 | 615 | ); |
617 | 616 | |
— | — | @@ -623,7 +622,7 @@ |
624 | 623 | top = this.$.position().top; |
625 | 624 | |
626 | 625 | this.cursor.initialBias = position.left > this.documentView.getNodeFromOffset( |
627 | | - this.selection.to, false |
| 626 | + selection.to, false |
628 | 627 | ).contentView.$.offset().left; |
629 | 628 | |
630 | 629 | do { |
— | — | @@ -652,18 +651,15 @@ |
653 | 652 | this.cursor.initialBias = direction === 'right' && unit === 'line' ? true : false; |
654 | 653 | } |
655 | 654 | |
656 | | - if ( this.keyboard.keys.shift && this.selection.from !== to) { |
657 | | - this.selection.to = to; |
658 | | - this.documentView.drawSelection( this.selection ); |
659 | | - this.hideCursor(); |
| 655 | + if ( this.keyboard.keys.shift && selection.from !== to) { |
| 656 | + selection.to = to; |
660 | 657 | } else { |
661 | | - if ( this.selection.from !== this.selection.to ) { |
| 658 | + if ( selection.from !== selection.to ) { |
662 | 659 | this.documentView.clearSelection(); |
663 | 660 | } |
664 | | - this.selection.from = this.selection.to = to; |
665 | | - this.showCursor(); |
| 661 | + selection.from = selection.to = to; |
666 | 662 | } |
667 | | - this.emitSelect(); |
| 663 | + this.model.select( selection ); |
668 | 664 | }; |
669 | 665 | |
670 | 666 | /** |
— | — | @@ -673,10 +669,12 @@ |
674 | 670 | * @param offset {Integer} Position to show the cursor at |
675 | 671 | */ |
676 | 672 | es.SurfaceView.prototype.showCursor = function() { |
677 | | - var position = this.documentView.getRenderedPositionFromOffset( |
678 | | - this.selection.to, this.cursor.initialBias |
679 | | - ); |
680 | | - this.cursor.$.css( { |
| 673 | + var $window = $( window ), |
| 674 | + position = this.documentView.getRenderedPositionFromOffset( |
| 675 | + this.currentSelection.to, this.cursor.initialBias |
| 676 | + ); |
| 677 | + |
| 678 | + this.$cursor.css( { |
681 | 679 | 'left': position.left, |
682 | 680 | 'top': position.top, |
683 | 681 | 'height': position.bottom - position.top |
— | — | @@ -690,9 +688,9 @@ |
691 | 689 | var inputTop = this.$input.offset().top, |
692 | 690 | inputBottom = inputTop + position.bottom - position.top; |
693 | 691 | if ( inputTop - this.dimensions.toolbarHeight < this.dimensions.scrollTop ) { |
694 | | - this.$window.scrollTop( inputTop - this.dimensions.toolbarHeight ); |
| 692 | + $window.scrollTop( inputTop - this.dimensions.toolbarHeight ); |
695 | 693 | } else if ( inputBottom > ( this.dimensions.scrollTop + this.dimensions.height ) ) { |
696 | | - this.$window.scrollTop( inputBottom - this.dimensions.height ); |
| 694 | + $window.scrollTop( inputBottom - this.dimensions.height ); |
697 | 695 | } |
698 | 696 | |
699 | 697 | // cursor blinking |
— | — | @@ -702,7 +700,7 @@ |
703 | 701 | |
704 | 702 | var _this = this; |
705 | 703 | this.cursor.interval = setInterval( function( surface ) { |
706 | | - _this.cursor.$.css( 'display', function( index, value ) { |
| 704 | + _this.$cursor.css( 'display', function( index, value ) { |
707 | 705 | return value === 'block' ? 'none' : 'block'; |
708 | 706 | } ); |
709 | 707 | }, 500 ); |
— | — | @@ -717,7 +715,7 @@ |
718 | 716 | if( this.cursor.interval ) { |
719 | 717 | clearInterval( this.cursor.interval ); |
720 | 718 | } |
721 | | - this.cursor.$.hide(); |
| 719 | + this.$cursor.hide(); |
722 | 720 | }; |
723 | 721 | |
724 | 722 | /* Inheritance */ |