r110892 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r110891‎ | r110892 | r110893 >
Date:22:59, 7 February 2012
Author:inez
Status:deferred
Tags:
Comment:
Cleanup Surface.js - only functionality that is needed for content editable
Modified paths:
  • /trunk/extensions/VisualEditor/demos/ce/main.js (modified) (history)
  • /trunk/extensions/VisualEditor/modules/ve/ce/ve.es.Surface.js (modified) (history)

Diff [purge]

Index: trunk/extensions/VisualEditor/demos/ce/main.js
@@ -4,225 +4,17 @@
55 'type': 'document',
66 'children': [
77 {
8 - 'type': 'heading',
9 - 'attributes': { 'level': 1 },
10 - 'content': { 'text': 'Direct manipulation interface' }
11 - },
12 - {
138 'type': 'paragraph',
14 - 'content': {
15 - 'text': 'In computer science, direct manipulation is a human-computer interaction style which involves continuous representation of objects of interest, and rapid, reversible, incremental actions and feedback. The intention is to allow a user to directly manipulate objects presented to them, using actions that correspond at least loosely to the physical world. An example of direct-manipulation is resizing a graphical shape, such as a rectangle, by dragging its corners or edges with a mouse.',
16 - 'annotations': [
17 - {
18 - 'type': 'link/internal',
19 - 'data': {
20 - 'title': 'Computer_science'
21 - },
22 - 'range': {
23 - 'start': 3,
24 - 'end': 19
25 - }
26 - },
27 - {
28 - 'type': 'link/internal',
29 - 'data': {
30 - 'title': 'Human-computer interaction'
31 - },
32 - 'range': {
33 - 'start': 46,
34 - 'end': 72
35 - }
36 - }
37 - ]
38 - }
39 - },
40 - {
41 - 'type': 'paragraph',
429 'content': { 'text': 'Having real-world metaphors for objects and actions can make it easier for a user to learn and use an interface (some might say that the interface is more natural or intuitive), and rapid, incremental feedback allows a user to make fewer errors and complete tasks in less time, because they can see the results of an action before completing the action, thus evaluating the output and compensating for mistakes.' }
4310 },
4411 {
4512 'type': 'paragraph',
46 - 'content': {
47 - 'text': 'The term was introduced by Ben Shneiderman in 1983 within the context of office applications and the desktop metaphor. Individuals in academia and computer scientists doing research on future user interfaces often put as much or even more stress on tactile control and feedback, or sonic control and feedback than on the visual feedback given by most GUIs. As a result the term direct manipulation interface has been more widespread in these environments. ',
48 - 'annotations': [
49 - {
50 - 'type': 'link/internal',
51 - 'data': {
52 - 'title': 'Ben_Shneiderman'
53 - },
54 - 'range': {
55 - 'start': 27,
56 - 'end': 42
57 - }
58 - },
59 - {
60 - 'type': 'link/internal',
61 - 'data': {
62 - 'title': 'GUI'
63 - },
64 - 'range': {
65 - 'start': 352,
66 - 'end': 356
67 - }
68 - },
69 - {
70 - 'type': 'object/hook',
71 - 'data': {
72 - 'html': '<sup><small><a href="#">[1]</a></small></sup>'
73 - },
74 - 'range': {
75 - 'start': 118,
76 - 'end': 119
77 - }
78 - },
79 - {
80 - 'type': 'object/template',
81 - 'data': {
82 - 'html': '<sup><small>[<a href="#">citation needed</a>]</small></sup>'
83 - },
84 - 'range': {
85 - 'start': 456,
86 - 'end': 457
87 - }
88 - }
89 - ]
90 - }
 13+ 'content': { 'text': 'Test 1' }
9114 },
9215 {
93 - 'type': 'heading',
94 - 'attributes': { 'level': 2 },
95 - 'content': { 'text': 'In contrast to WIMP/GUI interfaces' }
96 - },
97 - {
9816 'type': 'paragraph',
99 - 'content': {
100 - 'text': 'Direct manipulation is closely associated with interfaces that use windows, icons, menus, and a pointing device (WIMP GUI) as these almost always incorporate direct manipulation to at least some degree. However, direct manipulation should not be confused with these other terms, as it does not imply the use of windows or even graphical output. For example, direct manipulation concepts can be applied to interfaces for blind or vision-impaired users, using a combination of tactile and sonic devices and software.',
101 - 'annotations': [
102 - {
103 - 'type': 'link/internal',
104 - 'data': {
105 - 'title': 'WIMP_(computing)'
106 - },
107 - 'range': {
108 - 'start': 113,
109 - 'end': 117
110 - }
111 - }
112 - ]
113 - }
114 - },
115 - {
116 - 'type': 'paragraph',
117 - 'content': {
118 - 'text': 'It is also possible to design a WIMP interface that intentionally does not make use of direct manipulation. For example, most versions of windowing interfaces (e.g. Microsoft Windows) allowed users to reposition a window by dragging it with the mouse, but would not continually redraw the complete window at intermediate positions during the drag. Instead, for example, a rectangular outline of the window might be drawn during the drag, with the complete window contents being redrawn only once the user had released the mouse button. This was necessary on older computers that lacked the memory and/or CPU power to quickly redraw data behind a window that was being dragged.',
119 - 'annotations': [
120 - {
121 - 'type': 'link/internal',
122 - 'data': {
123 - 'title': 'Microsoft_Windows'
124 - },
125 - 'range': {
126 - 'start': 165,
127 - 'end': 182
128 - }
129 - }
130 - ]
131 - }
132 - },
133 - {
134 - 'type': 'heading',
135 - 'attributes': { 'level': 2 },
136 - 'content': { 'text': 'In point of sale graphic interfaces' }
137 - },
138 - {
139 - 'type': 'paragraph',
140 - 'content': {
141 - 'text': 'The ViewTouch graphic touchscreen POS (point of sale) GUI developed by Gene Mosher on the Atari ST computer and first installed in restaurants in 1986 is an early example of an application specific GUI that manifests all of the characteristics of direct manipulation.'
142 - }
143 - },
144 - {
145 - 'type': 'paragraph',
146 - 'content': {
147 - 'text': 'Mosher\'s POS touchscreen GUI has been widely copied and is in universal use on virtually all modern point of sale displays. Even in its earliest form it contained such features as \'lighting up\' both selected \'buttons\' (i.e., widgets) and \'tab\' buttons which indicated the user\'s current position in the transaction as the user navigated among the application\'s pages.'
148 - }
149 - },
150 - {
151 - 'type': 'paragraph',
152 - 'content': {
153 - 'text': 'In 1995 the ViewTouch GUI was developed into an X Window System window manager, extending the usefulness of the direct manipulation interface to users equipped with no other equipment than networked displays relying on the X network display protocol. This application is a practical and useful example of the benefit of the direct manipulation interface. Users are freed from the requirement of making use of keyboards, mice and even local computers themselves while they are simultaneously empowered to work in collaborative fashion with each other in world wide virtual workgroups by merely interacting with the framework of graphical symbols on the networked touchscreen.'
154 - }
155 - },
156 - {
157 - 'type': 'heading',
158 - 'attributes': { 'level': 2 },
159 - 'content': { 'text': 'In computer graphics' }
160 - },
161 - {
162 - 'type': 'paragraph',
163 - 'content': {
164 - 'text': 'Because of the difficulty of visualizing and manipulating various aspects of computer graphics, including geometry creation and editing, animation, layout of objects and cameras, light placement, and other effects, direct manipulation is an extremely important part of 3D computer graphics. There are standard direct manipulation widgets as well as many unique widgets that are developed either as a better solution to an old problem or as a solution for a new and/or unique problem. The widgets attempt to allow the user to modify an object in any possible direction while also providing easy guides or constraints to allow the user to easily modify an object in the most common directions, while also attempting to be as intuitive as to the function of the widget as possible. The three most ubiquitous transformation widgets are mostly standardized and are:'
165 - }
166 - },
167 - {
168 - 'type': 'list',
169 - 'children': [
170 - {
171 - 'type': 'listItem',
172 - 'attributes': {
173 - 'styles': ['bullet']
174 - },
175 - 'children' : [
176 - {
177 - 'type': 'paragraph',
178 - 'content': { 'text': 'the translation widget, which usually consists of three arrows aligned with the orthogonal axes centered on the object to be translated. Dragging the center of the widget translates the object directly underneath the mouse pointer in the plane parallel to the camera plane, while dragging any of the three arrows translates the object along the appropriate axis. The axes may be aligned with the world-space axes, the object-space axes, or some other space.' }
179 - }
180 - ]
181 - },
182 - {
183 - 'type': 'listItem',
184 - 'attributes': {
185 - 'styles': ['bullet']
186 - },
187 - 'children' : [
188 - {
189 - 'type': 'paragraph',
190 - 'content': { 'text': 'the rotation widget, which usually consists of three circles aligned with the three orthogonal axes, and one circle aligned with the camera plane. Dragging any of the circles rotates the object around the appropriate axis, while dragging elsewhere will freely rotate the object (virtual trackball rotation).' }
191 - }
192 - ]
193 - },
194 - {
195 - 'type': 'listItem',
196 - 'attributes': {
197 - 'styles': ['bullet']
198 - },
199 - 'children' : [
200 - {
201 - 'type': 'paragraph',
202 - 'content': { 'text': 'the scale widget, which usually consists of three short lines aligned with the orthogonal axes terminating in boxes, and one box in the center of the widget. Dragging any of the three axis-aligned boxes effects a non-uniform scale along solely that axis, while dragging the center box effects a uniform scale on all three axes at once.' }
203 - }
204 - ]
205 - }
206 -
207 - ]
208 - },
209 - {
210 - 'type': 'paragraph',
211 - 'content': {
212 - 'text': 'Depending on the specific common uses of an object, different kinds of widgets may be used. For example, a light in computer graphics is, like any other object, also defined by a transformation (translation and rotation), but it is sometimes positioned and directed simply with its endpoint positions because it may be more intuitive to define the position of the light source and then define the light\'s target, rather than rotating it around the coordinate axes in order to point it at a known position.'
213 - }
214 - },
215 - {
216 - 'type': 'paragraph',
217 - 'content': {
218 - 'text': 'Other widgets may be unique for a particular tool, such as edge controls to change the cone of a spotlight, points and handles to define the position and tangent vector for a spline control point, circles of variable size to define a blur filter width or paintbrush size, IK targets for hands and feet, or color wheels and swatches for quickly choosing colors. Complex widgets may even incorporate some techniques from scientific visualization to efficiently present relevant data (such as vector fields for particle effects or false color images to display vertex maps).'
219 - }
220 - },
221 - {
222 - 'type': 'paragraph',
223 - 'content': {
224 - 'text': 'Direct manipulation, as well as user interface design in general, for 3D computer graphics tasks, is still an active area of invention and innovation, as the process of generating CG images is generally not considered to be intuitive or easy in comparison to the difficulty of what the user wants to do, especially for complex tasks. The user interface for word processing, for example, is easy to learn for new users and is sufficient for most word processing tasks, so it is a mostly solved and standardized UI, while the user interfaces for 3D computer graphics are usually either difficult to learn and use and not sufficiently powerful for complex tasks, or sufficiently powerful but extremely difficult to learn and use, so direct manipulation and user interfaces will vary wildly from application to application.'
225 - }
226 - }
 17+ 'content': { 'text': 'Test 2' }
 18+ }
22719 ]
22820 },
22921 'Formatting': {
Index: trunk/extensions/VisualEditor/modules/ve/ce/ve.es.Surface.js
@@ -17,159 +17,19 @@
1818
1919 // Properties
2020 this.model = model;
21 - this.currentSelection = new ve.Range();
2221 this.documentView = new ve.es.DocumentNode( this.model.getDocument(), this );
2322 this.contextView = null;
2423 this.$ = $container
2524 .addClass( 'es-surfaceView' )
2625 .append( this.documentView.$ );
27 - this.$input = $( '<textarea class="es-surfaceView-textarea" autocapitalize="off" />' )
28 - .appendTo( 'body' );
29 - this.$cursor = $( '<div class="es-surfaceView-cursor"></div>' )
30 - .appendTo( 'body' );
31 - this.insertionAnnotations = [];
32 - this.updateSelectionTimeout = undefined;
3326 this.emitUpdateTimeout = undefined;
34 - this.emitCursorTimeout = undefined;
3527
36 - // Interaction states
37 -
38 - /*
39 - * There are three different selection modes available for mouse. Selection of:
40 - * 1 - chars
41 - * 2 - words
42 - * 3 - nodes (e.g. paragraph, listitem)
43 - *
44 - * In case of 2 and 3 selectedRange stores the range of original selection caused by double
45 - * or triple mousedowns.
46 - */
47 - this.mouse = {
48 - selectingMode: null,
49 - selectedRange: null
50 - };
51 - this.cursor = {
52 - interval: null,
53 - initialLeft: null,
54 - initialBias: false
55 - };
56 - this.keyboard = {
57 - selecting: false,
58 - cursorAnchor: null,
59 - keydownTimeout: null,
60 - keys: { shift: false }
61 - };
62 - this.dimensions = {
63 - width: this.$.width(),
64 - height: $window.height(),
65 - scrollTop: $window.scrollTop(),
66 - // XXX: This is a dirty hack!
67 - toolbarHeight: $( '#es-toolbar' ).height()
68 - };
69 -
7028 // Events
71 - /*
72 - this.model.on( 'select', function( selection ) {
73 - // Keep a copy of the current selection on hand
74 - _this.currentSelection = selection.clone();
75 - // Respond to selection changes
76 - _this.updateSelection();
77 - if ( selection.getLength() ) {
78 - _this.$input.val( _this.documentView.model.getContentText( selection ) ).select();
79 - _this.clearInsertionAnnotations();
80 - } else {
81 - _this.$input.val('').select();
82 - _this.loadInsertionAnnotations();
83 - }
84 - } );
85 - */
8629 this.model.getDocument().on( 'update', function() {
87 - //_this.emitUpdate( 25 );
 30+ _this.emitUpdate( 25 );
8831 } );
89 - this.on( 'update', function() {
90 - //_this.updateSelection( 25 );
91 - } );
92 - this.$.mousedown( function(e) {
93 - //return _this.onMouseDown( e );
94 - } );
95 - this.$input.bind( {
96 - 'focus': function() {
97 - // Make sure we aren't double-binding
98 - $document.unbind( '.es-surfaceView' );
99 - // Bind mouse and key events to the document to ensure we don't miss anything
100 - $document.bind( {
101 - 'mousemove.es-surfaceView': function(e) {
102 - return _this.onMouseMove( e );
103 - },
104 - 'mouseup.es-surfaceView': function(e) {
105 - return _this.onMouseUp( e );
106 - },
107 - 'keydown.es-surfaceView': function( e ) {
108 - return _this.onKeyDown( e );
109 - },
110 - 'keyup.es-surfaceView': function( e ) {
111 - return _this.onKeyUp( e );
112 - },
113 - 'copy.es-surfaceView': function( e ) {
114 - return _this.onCopy( e );
115 - },
116 - 'cut.es-surfaceView': function( e ) {
117 - return _this.onCut( e );
118 - },
119 - 'paste.es-surfaceView': function( e ) {
120 - return _this.onPaste( e );
121 - }
122 - } );
123 - },
124 - 'blur': function( e ) {
125 - // Release our event handlers when not focused
126 - $document.unbind( '.es-surfaceView' );
127 - _this.hideCursor();
128 - },
129 - 'paste': function() {
130 - setTimeout( function() {
131 - _this.model.breakpoint();
132 - _this.insertFromInput();
133 - _this.model.breakpoint();
134 - }, 0 );
135 - }
136 - } );
137 - $window.bind( {
138 - 'resize': function() {
139 - // Re-render when resizing horizontally
140 - // TODO: Instead of re-rendering on every single 'resize' event wait till user is done
141 - // with resizing - can be implemented with setTimeout
142 - _this.hideCursor();
143 - _this.dimensions.height = $window.height();
144 - // XXX: This is a dirty hack!
145 - _this.dimensions.toolbarHeight = $( '#es-toolbar' ).height();
146 - var width = _this.$.width();
147 - if ( _this.dimensions.width !== width ) {
148 - _this.dimensions.width = width;
149 - _this.documentView.renderContent();
150 - _this.emitUpdate( 25 );
151 - }
152 - },
153 - 'scroll': function() {
154 - _this.dimensions.scrollTop = $window.scrollTop();
155 - if ( _this.contextView ) {
156 - if ( _this.currentSelection.getLength() && !_this.mouse.selectingMode ) {
157 - _this.contextView.set();
158 - } else {
159 - _this.contextView.clear();
160 - }
161 - }
162 - },
163 - 'blur': function() {
164 - _this.keyboard.keys.shift = false;
165 - }
166 - } );
16732
168 - // Configuration
169 - this.mac = navigator.userAgent.match(/mac/i) ? true : false; // (yes it's evil, for keys only!)
170 - this.ie8 = $.browser.msie && $.browser.version === "8.0";
171 -
17233 // Initialization
173 - this.$input.focus();
17434 this.documentView.renderContent();
17535 };
17636
@@ -179,141 +39,10 @@
18040 this.contextView = contextView;
18141 };
18242
183 -ve.es.Surface.prototype.getContextView = function() {
184 - return this.contextView ;
185 -};
186 -
187 -ve.es.Surface.prototype.annotate = function( method, annotation ) {
188 - if ( method === 'toggle' ) {
189 - var annotations = this.getAnnotations();
190 - if ( ve.dm.DocumentNode.getIndexOfAnnotation( annotations.full, annotation ) !== -1 ) {
191 - method = 'clear';
192 - } else {
193 - method = 'set';
194 - }
195 - }
196 - if ( this.currentSelection.getLength() ) {
197 - var tx = this.model.getDocument().prepareContentAnnotation(
198 - this.currentSelection, method, annotation
199 - );
200 - this.model.transact( tx );
201 - } else {
202 - if ( method === 'set' ) {
203 - this.addInsertionAnnotation( annotation );
204 - } else if ( method === 'clear' ) {
205 - this.removeInsertionAnnotation( annotation );
206 - }
207 - }
208 -};
209 -
210 -ve.es.Surface.prototype.getAnnotations = function() {
211 - return this.currentSelection.getLength() ?
212 - this.model.getDocument().getAnnotationsFromRange( this.currentSelection ) :
213 - {
214 - 'full': this.insertionAnnotations,
215 - 'partial': [],
216 - 'all': this.insertionAnnotations
217 - };
218 -};
219 -
220 -ve.es.Surface.prototype.emitCursor = function() {
221 - if ( this.emitCursorTimeout ) {
222 - clearTimeout( this.emitCursorTimeout );
223 - }
224 - var _this = this;
225 - this.emitCursorTimeout = setTimeout( function() {
226 - var annotations = _this.getAnnotations(),
227 - nodes = [],
228 - model = _this.documentView.model;
229 - if ( _this.currentSelection.from === _this.currentSelection.to ) {
230 - nodes.push( model.getNodeFromOffset( _this.currentSelection.from ) );
231 - } else {
232 - var startNode = model.getNodeFromOffset( _this.currentSelection.start ),
233 - endNode = model.getNodeFromOffset( _this.currentSelection.end );
234 - if ( startNode === endNode ) {
235 - nodes.push( startNode );
236 - } else {
237 - model.traverseLeafNodes( function( node ) {
238 - nodes.push( node );
239 - if( node === endNode ) {
240 - return false;
241 - }
242 - }, startNode );
243 - }
244 - }
245 - _this.emit( 'cursor', annotations, nodes );
246 - }, 50 );
247 -};
248 -
249 -ve.es.Surface.prototype.getInsertionAnnotations = function() {
250 - return this.insertionAnnotations;
251 -};
252 -
253 -ve.es.Surface.prototype.addInsertionAnnotation = function( annotation ) {
254 - this.insertionAnnotations.push( annotation );
255 - this.emitCursor();
256 -};
257 -
258 -ve.es.Surface.prototype.loadInsertionAnnotations = function( annotation ) {
259 - this.insertionAnnotations =
260 - this.model.getDocument().getAnnotationsFromOffset( this.currentSelection.to - 1 );
261 - // Filter out annotations that aren't textStyles or links
262 - for ( var i = 0; i < this.insertionAnnotations.length; i++ ) {
263 - if ( !this.insertionAnnotations[i].type.match( /(textStyle\/|link\/)/ ) ) {
264 - this.insertionAnnotations.splice( i, 1 );
265 - i--;
266 - }
267 - }
268 - this.emitCursor();
269 -};
270 -
271 -ve.es.Surface.prototype.removeInsertionAnnotation = function( annotation ) {
272 - var index = ve.dm.DocumentNode.getIndexOfAnnotation( this.insertionAnnotations, annotation );
273 - if ( index !== -1 ) {
274 - this.insertionAnnotations.splice( index, 1 );
275 - }
276 - this.emitCursor();
277 -};
278 -
279 -ve.es.Surface.prototype.clearInsertionAnnotations = function() {
280 - this.insertionAnnotations = [];
281 - this.emitCursor();
282 -};
283 -
28443 ve.es.Surface.prototype.getModel = function() {
28544 return this.model;
28645 };
28746
288 -ve.es.Surface.prototype.updateSelection = function( delay ) {
289 - var _this = this;
290 - function update() {
291 - if ( _this.currentSelection.getLength() ) {
292 - _this.clearInsertionAnnotations();
293 - _this.hideCursor();
294 - _this.documentView.drawSelection( _this.currentSelection );
295 - } else {
296 - _this.showCursor();
297 - _this.documentView.clearSelection( _this.currentSelection );
298 - }
299 - if ( _this.contextView ) {
300 - if ( _this.currentSelection.getLength() && !_this.mouse.selectingMode ) {
301 - _this.contextView.set();
302 - } else {
303 - _this.contextView.clear();
304 - }
305 - }
306 - _this.updateSelectionTimeout = undefined;
307 - }
308 - if ( delay ) {
309 - if ( this.updateSelectionTimeout !== undefined ) {
310 - return;
311 - }
312 - this.updateSelectionTimeout = setTimeout( update, delay );
313 - } else {
314 - update();
315 - }
316 -};
317 -
31847 ve.es.Surface.prototype.emitUpdate = function( delay ) {
31948 if ( delay ) {
32049 if ( this.emitUpdateTimeout !== undefined ) {
@@ -329,692 +58,6 @@
33059 }
33160 };
33261
333 -ve.es.Surface.prototype.onMouseDown = function( e ) {
334 - // Only for left mouse button
335 - if ( e.which === 1 ) {
336 - var selection = this.currentSelection.clone(),
337 - offset = this.documentView.getOffsetFromEvent( e );
338 - // Single click
339 - if ( this.ie8 || e.originalEvent.detail === 1 ) {
340 - // @see {ve.es.Surface.prototype.onMouseMove}
341 - this.mouse.selectingMode = 1;
342 -
343 - if ( this.keyboard.keys.shift && offset !== selection.from ) {
344 - // Extend current or create new selection
345 - selection.to = offset;
346 - } else {
347 - selection.from = selection.to = offset;
348 -
349 - var position = ve.Position.newFromEventPagePosition( e ),
350 - nodeView = this.documentView.getNodeFromOffset( offset, false );
351 - this.cursor.initialBias = position.left > nodeView.contentView.$.offset().left;
352 - }
353 - }
354 - // Double click
355 - else if ( e.originalEvent.detail === 2 ) {
356 - // @see {ve.es.Surface.prototype.onMouseMove}
357 - this.mouse.selectingMode = 2;
358 -
359 - var wordRange = this.model.getDocument().getWordBoundaries( offset );
360 - if( wordRange ) {
361 - selection = wordRange;
362 - this.mouse.selectedRange = selection.clone();
363 - }
364 - }
365 - // Triple click
366 - else if ( e.originalEvent.detail >= 3 ) {
367 - // @see {ve.es.Surface.prototype.onMouseMove}
368 - this.mouse.selectingMode = 3;
369 -
370 - var node = this.documentView.getNodeFromOffset( offset ),
371 - nodeOffset = this.documentView.getOffsetFromNode( node, false );
372 -
373 - selection.from = this.model.getDocument().getRelativeContentOffset( nodeOffset, 1 );
374 - selection.to = this.model.getDocument().getRelativeContentOffset(
375 - nodeOffset + node.getElementLength(), -1
376 - );
377 - this.mouse.selectedRange = selection.clone();
378 - }
379 - }
380 -
381 - var _this = this;
382 -
383 - function select() {
384 - if ( e.which === 1 ) {
385 - // Reset the initial left position
386 - _this.cursor.initialLeft = null;
387 - // Apply new selection
388 - _this.model.select( selection, true );
389 - }
390 -
391 - // If the inut isn't already focused, focus it and select it's contents
392 - if ( !_this.$input.is( ':focus' ) ) {
393 - _this.$input.focus().select();
394 - }
395 - }
396 -
397 - if ( this.ie8 ) {
398 - setTimeout( select, 0 );
399 - } else {
400 - select();
401 - }
402 -
403 - return false;
404 -};
405 -
406 -ve.es.Surface.prototype.onMouseMove = function( e ) {
407 - // Only with the left mouse button while in selecting mode
408 - if ( e.which === 1 && this.mouse.selectingMode ) {
409 - var selection = this.currentSelection.clone(),
410 - offset = this.documentView.getOffsetFromEvent( e );
411 -
412 - // Character selection
413 - if ( this.mouse.selectingMode === 1 ) {
414 - selection.to = offset;
415 - }
416 - // Word selection
417 - else if ( this.mouse.selectingMode === 2 ) {
418 - var wordRange = this.model.getDocument().getWordBoundaries( offset );
419 - if ( wordRange ) {
420 - if ( wordRange.to <= this.mouse.selectedRange.from ) {
421 - selection.from = wordRange.from;
422 - selection.to = this.mouse.selectedRange.to;
423 - } else {
424 - selection.from = this.mouse.selectedRange.from;
425 - selection.to = wordRange.to;
426 - }
427 - }
428 - }
429 - // Node selection
430 - else if ( this.mouse.selectingMode === 3 ) {
431 - // @see {ve.es.Surface.prototype.onMouseMove}
432 - this.mouse.selectingMode = 3;
433 -
434 - var nodeRange = this.documentView.getRangeFromNode(
435 - this.documentView.getNodeFromOffset( offset )
436 - );
437 - if ( nodeRange.to <= this.mouse.selectedRange.from ) {
438 - selection.from = this.model.getDocument().getRelativeContentOffset(
439 - nodeRange.from, 1
440 - );
441 - selection.to = this.mouse.selectedRange.to;
442 - } else {
443 - selection.from = this.mouse.selectedRange.from;
444 - selection.to = this.model.getDocument().getRelativeContentOffset(
445 - nodeRange.to, -1
446 - );
447 - }
448 - }
449 - // Apply new selection
450 - this.model.select( selection, true );
451 - }
452 -};
453 -
454 -ve.es.Surface.prototype.onMouseUp = function( e ) {
455 - if ( e.which === 1 ) { // left mouse button
456 - this.mouse.selectingMode = this.mouse.selectedRange = null;
457 - this.model.select( this.currentSelection, true );
458 - if ( this.contextView ) {
459 - // We have to manually call this because the selection will not have changed between the
460 - // most recent mousemove and this mouseup
461 - this.contextView.set();
462 - }
463 - }
464 -};
465 -
466 -ve.es.Surface.prototype.onCopy = function( e ) {
467 - // TODO: Keep a data copy around
468 - return true;
469 -};
470 -
471 -ve.es.Surface.prototype.onCut = function( e ) {
472 - var _this = this;
473 - setTimeout( function() {
474 - _this.handleDelete();
475 - }, 10 );
476 - return true;
477 -};
478 -
479 -ve.es.Surface.prototype.onPaste = function( e ) {
480 - // TODO: Check if the data copy is the same as what got pasted, and use that instead if so
481 - return true;
482 -};
483 -
484 -ve.es.Surface.prototype.onKeyDown = function( e ) {
485 - switch ( e.keyCode ) {
486 - // Tab
487 - case 9:
488 - if ( !e.metaKey && !e.ctrlKey && !e.altKey ) {
489 - this.$input.val( '\t' );
490 - this.handleInsert();
491 - e.preventDefault();
492 - return false;
493 - }
494 - return true;
495 - // Shift
496 - case 16:
497 - this.keyboard.keys.shift = true;
498 - this.keyboard.selecting = true;
499 - break;
500 - // Ctrl
501 - case 17:
502 - break;
503 - // Home
504 - case 36:
505 - this.moveCursor( 'left', 'line' );
506 - break;
507 - // End
508 - case 35:
509 - this.moveCursor( 'right', 'line' );
510 - break;
511 - // Left arrow
512 - case 37:
513 - if ( !this.mac ) {
514 - if ( e.ctrlKey ) {
515 - this.moveCursor( 'left', 'word' );
516 - } else {
517 - this.moveCursor( 'left', 'char' );
518 - }
519 - } else {
520 - if ( e.metaKey || e.ctrlKey ) {
521 - this.moveCursor( 'left', 'line' );
522 - } else if ( e.altKey ) {
523 - this.moveCursor( 'left', 'word' );
524 - } else {
525 - this.moveCursor( 'left', 'char' );
526 - }
527 - }
528 - break;
529 - // Up arrow
530 - case 38:
531 - if ( !this.mac ) {
532 - if ( e.ctrlKey ) {
533 - this.moveCursor( 'up', 'unit' );
534 - } else {
535 - this.moveCursor( 'up', 'char' );
536 - }
537 - } else {
538 - if ( e.altKey ) {
539 - this.moveCursor( 'up', 'unit' );
540 - } else {
541 - this.moveCursor( 'up', 'char' );
542 - }
543 - }
544 - break;
545 - // Right arrow
546 - case 39:
547 - if ( !this.mac ) {
548 - if ( e.ctrlKey ) {
549 - this.moveCursor( 'right', 'word' );
550 - } else {
551 - this.moveCursor( 'right', 'char' );
552 - }
553 - } else {
554 - if ( e.metaKey || e.ctrlKey ) {
555 - this.moveCursor( 'right', 'line' );
556 - } else if ( e.altKey ) {
557 - this.moveCursor( 'right', 'word' );
558 - } else {
559 - this.moveCursor( 'right', 'char' );
560 - }
561 - }
562 - break;
563 - // Down arrow
564 - case 40:
565 - if ( !this.mac ) {
566 - if ( e.ctrlKey ) {
567 - this.moveCursor( 'down', 'unit' );
568 - } else {
569 - this.moveCursor( 'down', 'char' );
570 - }
571 - } else {
572 - if ( e.altKey ) {
573 - this.moveCursor( 'down', 'unit' );
574 - } else {
575 - this.moveCursor( 'down', 'char' );
576 - }
577 - }
578 - break;
579 - // Backspace
580 - case 8:
581 - this.handleDelete( true );
582 - break;
583 - // Delete
584 - case 46:
585 - this.handleDelete();
586 - break;
587 - // Enter
588 - case 13:
589 - if ( this.keyboard.keys.shift ) {
590 - this.$input.val( '\n' );
591 - this.handleInsert();
592 - e.preventDefault();
593 - return false;
594 - }
595 - this.handleEnter();
596 - e.preventDefault();
597 - break;
598 - // Insert content (maybe)
599 - default:
600 - // Control/command + character combos
601 - if ( e.metaKey || e.ctrlKey ) {
602 - switch ( e.keyCode ) {
603 - // y (redo)
604 - case 89:
605 - this.model.redo();
606 - return false;
607 - // z (undo/redo)
608 - case 90:
609 - if ( this.keyboard.keys.shift ) {
610 - this.model.redo();
611 - } else {
612 - this.model.undo();
613 - }
614 - return false;
615 - // a (select all)
616 - case 65:
617 - this.model.select( new ve.Range(
618 - this.model.getDocument().getRelativeContentOffset( 0, 1 ),
619 - this.model.getDocument().getRelativeContentOffset(
620 - this.model.getDocument().getContentLength(), -1
621 - )
622 - ), true );
623 - return false;
624 - // b (bold)
625 - case 66:
626 - this.annotate( 'toggle', {'type': 'textStyle/bold' } );
627 - return false;
628 - // i (italic)
629 - case 73:
630 - this.annotate( 'toggle', {'type': 'textStyle/italic' } );
631 - return false;
632 - // k (hyperlink)
633 - case 75:
634 - if ( this.currentSelection.getLength() ) {
635 - this.contextView.openInspector( 'link' );
636 - } else {
637 - var range = this.model.getDocument().getAnnotationBoundaries(
638 - this.currentSelection.from, { 'type': 'link/internal' }, true
639 - );
640 - if ( range ) {
641 - this.model.select( range );
642 - this.contextView.openInspector( 'link' );
643 - }
644 - }
645 - return false;
646 - }
647 - }
648 - // Regular text insertion
649 - this.handleInsert();
650 - break;
651 - }
652 - return true;
653 -};
654 -
655 -ve.es.Surface.prototype.onKeyUp = function( e ) {
656 - if ( e.keyCode === 16 ) {
657 - this.keyboard.keys.shift = false;
658 - if ( this.keyboard.selecting ) {
659 - this.keyboard.selecting = false;
660 - }
661 - }
662 -};
663 -
664 -ve.es.Surface.prototype.handleInsert = function() {
665 - var _this = this;
666 - if ( _this.keyboard.keydownTimeout ) {
667 - clearTimeout( _this.keyboard.keydownTimeout );
668 - }
669 - _this.keyboard.keydownTimeout = setTimeout( function () {
670 - _this.insertFromInput();
671 - }, 10 );
672 -};
673 -
674 -ve.es.Surface.prototype.handleDelete = function( backspace, isPartial ) {
675 - var selection = this.currentSelection.clone(),
676 - sourceOffset,
677 - targetOffset,
678 - sourceSplitableNode,
679 - targetSplitableNode,
680 - tx;
681 - if ( selection.from === selection.to ) {
682 - if ( backspace ) {
683 - sourceOffset = selection.to;
684 - targetOffset = this.model.getDocument().getRelativeContentOffset(
685 - sourceOffset,
686 - -1
687 - );
688 - } else {
689 - sourceOffset = this.model.getDocument().getRelativeContentOffset(
690 - selection.to,
691 - 1
692 - );
693 - targetOffset = selection.to;
694 - }
695 -
696 - var sourceNode = this.documentView.getNodeFromOffset( sourceOffset, false ),
697 - targetNode = this.documentView.getNodeFromOffset( targetOffset, false );
698 -
699 - if ( sourceNode.model.getElementType() === targetNode.model.getElementType() ) {
700 - sourceSplitableNode = ve.es.Node.getSplitableNode( sourceNode );
701 - targetSplitableNode = ve.es.Node.getSplitableNode( targetNode );
702 - }
703 -
704 - selection.from = selection.to = targetOffset;
705 - this.model.select( selection );
706 -
707 - if ( sourceNode === targetNode ||
708 - ( typeof sourceSplitableNode !== 'undefined' &&
709 - sourceSplitableNode.getParent() === targetSplitableNode.getParent() ) ) {
710 - tx = this.model.getDocument().prepareRemoval(
711 - new ve.Range( targetOffset, sourceOffset )
712 - );
713 - this.model.transact( tx, isPartial );
714 - } else {
715 - tx = this.model.getDocument().prepareInsertion(
716 - targetOffset, sourceNode.model.getContentData()
717 - );
718 - this.model.transact( tx, isPartial );
719 -
720 - var nodeToDelete = sourceNode;
721 - ve.Node.traverseUpstream( nodeToDelete, function( node ) {
722 - if ( node.getParent().children.length === 1 ) {
723 - nodeToDelete = node.getParent();
724 - return true;
725 - } else {
726 - return false;
727 - }
728 - } );
729 - var range = new ve.Range();
730 - range.from = this.documentView.getOffsetFromNode( nodeToDelete, false );
731 - range.to = range.from + nodeToDelete.getElementLength();
732 - tx = this.model.getDocument().prepareRemoval( range );
733 - this.model.transact( tx, isPartial );
734 - }
735 - } else {
736 - // selection removal
737 - tx = this.model.getDocument().prepareRemoval( selection );
738 - this.model.transact( tx, isPartial );
739 - selection.from = selection.to = selection.start;
740 - this.model.select( selection );
741 - }
742 -};
743 -
744 -ve.es.Surface.prototype.handleEnter = function() {
745 - var selection = this.currentSelection.clone(),
746 - tx;
747 - if ( selection.from !== selection.to ) {
748 - this.handleDelete( false, true );
749 - }
750 - var node = this.documentView.getNodeFromOffset( selection.to, false ),
751 - nodeOffset = this.documentView.getOffsetFromNode( node, false );
752 -
753 - if (
754 - nodeOffset + node.getContentLength() + 1 === selection.to &&
755 - node === ve.es.Node.getSplitableNode( node )
756 - ) {
757 - tx = this.documentView.model.prepareInsertion(
758 - nodeOffset + node.getElementLength(),
759 - [ { 'type': 'paragraph' }, { 'type': '/paragraph' } ]
760 - );
761 - this.model.transact( tx );
762 - selection.from = selection.to = nodeOffset + node.getElementLength() + 1;
763 - } else {
764 - var stack = [],
765 - splitable = false;
766 -
767 - ve.Node.traverseUpstream( node, function( node ) {
768 - var elementType = node.model.getElementType();
769 - if (
770 - splitable === true &&
771 - ve.es.DocumentNode.splitRules[ elementType ].children === true
772 - ) {
773 - return false;
774 - }
775 - stack.splice(
776 - stack.length / 2,
777 - 0,
778 - { 'type': '/' + elementType },
779 - {
780 - 'type': elementType,
781 - 'attributes': ve.copyObject( node.model.element.attributes )
782 - }
783 - );
784 - splitable = ve.es.DocumentNode.splitRules[ elementType ].self;
785 - return true;
786 - } );
787 - tx = this.documentView.model.prepareInsertion( selection.to, stack );
788 - this.model.transact( tx );
789 - selection.from = selection.to =
790 - this.model.getDocument().getRelativeContentOffset( selection.to, 1 );
791 - }
792 - this.model.select( selection );
793 -};
794 -
795 -ve.es.Surface.prototype.insertFromInput = function() {
796 - var selection = this.currentSelection.clone(),
797 - val = this.$input.val();
798 - if ( val.length > 0 ) {
799 - // Check if there was any effective input
800 - var input = this.$input[0],
801 - // Internet Explorer
802 - range = document.selection && document.selection.createRange();
803 - if (
804 - // DOM 3.0
805 - ( 'selectionStart' in input && input.selectionEnd - input.selectionStart ) ||
806 - // Internet Explorer
807 - ( range && range.text.length )
808 - ) {
809 - // The input is still selected, so the key must not have inserted anything
810 - return;
811 - }
812 -
813 - // Clear the value for more input
814 - this.$input.val( '' );
815 -
816 - // Prepare and process a transaction
817 - var tx;
818 - if ( selection.from != selection.to ) {
819 - tx = this.model.getDocument().prepareRemoval( selection );
820 - this.model.transact( tx, true );
821 - selection.from = selection.to =
822 - Math.min( selection.from, selection.to );
823 - }
824 - var data = val.split('');
825 - ve.dm.DocumentNode.addAnnotationsToData( data, this.getInsertionAnnotations() );
826 - tx = this.model.getDocument().prepareInsertion( selection.from, data );
827 - this.model.transact( tx );
828 -
829 - // Move the selection
830 - selection.from += val.length;
831 - selection.to += val.length;
832 - this.model.select( selection );
833 - }
834 -};
835 -
836 -/**
837 - * @param {String} direction up | down | left | right
838 - * @param {String} unit char | word | line | node | page
839 - */
840 -ve.es.Surface.prototype.moveCursor = function( direction, unit ) {
841 - if ( direction !== 'up' && direction !== 'down' ) {
842 - this.cursor.initialLeft = null;
843 - }
844 - var selection = this.currentSelection.clone(),
845 - to,
846 - offset;
847 - switch ( direction ) {
848 - case 'left':
849 - case 'right':
850 - switch ( unit ) {
851 - case 'char':
852 - case 'word':
853 - if ( this.keyboard.keys.shift || selection.from === selection.to ) {
854 - offset = selection.to;
855 - } else {
856 - offset = direction === 'left' ? selection.start : selection.end;
857 - }
858 - to = this.model.getDocument().getRelativeContentOffset(
859 - offset,
860 - direction === 'left' ? -1 : 1
861 - );
862 - if ( unit === 'word' ) {
863 - var wordRange = this.model.getDocument().getWordBoundaries(
864 - direction === 'left' ? to : offset
865 - );
866 - if ( wordRange ) {
867 - to = direction === 'left' ? wordRange.start : wordRange.end;
868 - }
869 - }
870 - break;
871 - case 'line':
872 - offset = this.cursor.initialBias ?
873 - this.model.getDocument().getRelativeContentOffset(
874 - selection.to,
875 - -1) :
876 - selection.to;
877 - var range = this.documentView.getRenderedLineRangeFromOffset( offset );
878 - to = direction === 'left' ? range.start : range.end;
879 - break;
880 - default:
881 - throw new Error( 'unrecognized cursor movement unit' );
882 - break;
883 - }
884 - break;
885 - case 'up':
886 - case 'down':
887 - switch ( unit ) {
888 - case 'unit':
889 - var toNode = null;
890 - this.model.getDocument().traverseLeafNodes(
891 - function( node ) {
892 - var doNextChild = toNode === null;
893 - toNode = node;
894 - return doNextChild;
895 - },
896 - this.documentView.getNodeFromOffset( selection.to, false ).getModel(),
897 - direction === 'up' ? true : false
898 - );
899 - to = this.model.getDocument().getOffsetFromNode( toNode, false ) + 1;
900 - break;
901 - case 'char':
902 - /*
903 - * Looks for the in-document character position that would match up with the
904 - * same horizontal position - jumping a few pixels up/down at a time until we
905 - * reach the next/previous line
906 - */
907 - var position = this.documentView.getRenderedPositionFromOffset(
908 - selection.to,
909 - this.cursor.initialBias
910 - );
911 -
912 - if ( this.cursor.initialLeft === null ) {
913 - this.cursor.initialLeft = position.left;
914 - }
915 - var fakePosition = new ve.Position( this.cursor.initialLeft, position.top ),
916 - i = 0,
917 - step = direction === 'up' ? -5 : 5,
918 - top = this.$.position().top;
919 -
920 - this.cursor.initialBias = position.left > this.documentView.getNodeFromOffset(
921 - selection.to, false
922 - ).contentView.$.offset().left;
923 -
924 - do {
925 - i++;
926 - fakePosition.top += i * step;
927 - if ( fakePosition.top < top ) {
928 - break;
929 - } else if (
930 - fakePosition.top > top + this.dimensions.height +
931 - this.dimensions.scrollTop
932 - ) {
933 - break;
934 - }
935 - fakePosition = this.documentView.getRenderedPositionFromOffset(
936 - this.documentView.getOffsetFromRenderedPosition( fakePosition ),
937 - this.cursor.initialBias
938 - );
939 - fakePosition.left = this.cursor.initialLeft;
940 - } while ( position.top === fakePosition.top );
941 - to = this.documentView.getOffsetFromRenderedPosition( fakePosition );
942 - break;
943 - default:
944 - throw new Error( 'unrecognized cursor movement unit' );
945 - }
946 - break;
947 - default:
948 - throw new Error( 'unrecognized cursor direction' );
949 - }
950 -
951 - if( direction != 'up' && direction != 'down' ) {
952 - this.cursor.initialBias = direction === 'right' && unit === 'line' ? true : false;
953 - }
954 -
955 - if ( this.keyboard.keys.shift && selection.from !== to) {
956 - selection.to = to;
957 - } else {
958 - selection.from = selection.to = to;
959 - }
960 - this.model.select( selection, true );
961 -};
962 -
963 -/**
964 - * Shows the cursor in a new position.
965 - *
966 - * @method
967 - * @param offset {Integer} Position to show the cursor at
968 - */
969 -ve.es.Surface.prototype.showCursor = function() {
970 - var $window = $( window ),
971 - position = this.documentView.getRenderedPositionFromOffset(
972 - this.currentSelection.to, this.cursor.initialBias
973 - );
974 -
975 - this.$cursor.css( {
976 - 'left': position.left,
977 - 'top': position.top,
978 - 'height': position.bottom - position.top
979 - } ).show();
980 - this.$input.css({
981 - 'top': position.top,
982 - 'height': position.bottom - position.top
983 - });
984 -
985 - // Auto scroll to cursor
986 - var inputTop = this.$input.offset().top,
987 - inputBottom = inputTop + position.bottom - position.top;
988 - if ( inputTop - this.dimensions.toolbarHeight < this.dimensions.scrollTop ) {
989 - $window.scrollTop( inputTop - this.dimensions.toolbarHeight );
990 - } else if ( inputBottom > ( this.dimensions.scrollTop + this.dimensions.height ) ) {
991 - $window.scrollTop( inputBottom - this.dimensions.height );
992 - }
993 -
994 - // cursor blinking
995 - if ( this.cursor.interval ) {
996 - clearInterval( this.cursor.interval );
997 - }
998 -
999 - var _this = this;
1000 - this.cursor.interval = setInterval( function( surface ) {
1001 - _this.$cursor.css( 'display', function( index, value ) {
1002 - return value === 'block' ? 'none' : 'block';
1003 - } );
1004 - }, 500 );
1005 -};
1006 -
1007 -/**
1008 - * Hides the cursor.
1009 - *
1010 - * @method
1011 - */
1012 -ve.es.Surface.prototype.hideCursor = function() {
1013 - if( this.cursor.interval ) {
1014 - clearInterval( this.cursor.interval );
1015 - }
1016 - this.$cursor.hide();
1017 -};
1018 -
101962 /* Inheritance */
102063
102164 ve.extendClass( ve.es.Surface, ve.EventEmitter );

Status & tagging log