[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 /** 2 * These plugins provide extra functionality for interaction with textareas. 3 */ 4 ( function ( $ ) { 5 if ( document.selection && document.selection.createRange ) { 6 // On IE, patch the focus() method to restore the windows' scroll position 7 // (bug 32241) 8 $.fn.extend( { 9 focus: ( function ( jqFocus ) { 10 return function () { 11 var $w, state, result; 12 if ( arguments.length === 0 ) { 13 $w = $( window ); 14 state = { top: $w.scrollTop(), left: $w.scrollLeft() }; 15 result = jqFocus.apply( this, arguments ); 16 window.scrollTo( state.top, state.left ); 17 return result; 18 } 19 return jqFocus.apply( this, arguments ); 20 }; 21 }( $.fn.focus ) ) 22 } ); 23 } 24 25 $.fn.textSelection = function ( command, options ) { 26 var fn, 27 context, 28 hasWikiEditorSurface, // The alt edit surface needs to implement the WikiEditor API 29 needSave, 30 retval; 31 32 /** 33 * Helper function to get an IE TextRange object for an element 34 */ 35 function rangeForElementIE( e ) { 36 if ( e.nodeName.toLowerCase() === 'input' ) { 37 return e.createTextRange(); 38 } else { 39 var sel = document.body.createTextRange(); 40 sel.moveToElementText( e ); 41 return sel; 42 } 43 } 44 45 /** 46 * Helper function for IE for activating the textarea. Called only in the 47 * IE-specific code paths below; makes use of IE-specific non-standard 48 * function setActive() if possible to avoid screen flicker. 49 */ 50 function activateElementOnIE( element ) { 51 if ( element.setActive ) { 52 element.setActive(); // bug 32241: doesn't scroll 53 } else { 54 $( element ).focus(); // may scroll (but we patched it above) 55 } 56 } 57 58 fn = { 59 /** 60 * Get the contents of the textarea 61 */ 62 getContents: function () { 63 return this.val(); 64 }, 65 /** 66 * Set the contents of the textarea, replacing anything that was there before 67 */ 68 setContents: function ( content ) { 69 this.val( content ); 70 }, 71 /** 72 * Get the currently selected text in this textarea. Will focus the textarea 73 * in some browsers (IE/Opera) 74 */ 75 getSelection: function () { 76 var retval, range, 77 el = this.get( 0 ); 78 79 if ( !el || $( el ).is( ':hidden' ) ) { 80 retval = ''; 81 } else if ( document.selection && document.selection.createRange ) { 82 activateElementOnIE( el ); 83 range = document.selection.createRange(); 84 retval = range.text; 85 } else if ( el.selectionStart || el.selectionStart === 0 ) { 86 retval = el.value.substring( el.selectionStart, el.selectionEnd ); 87 } 88 89 return retval; 90 }, 91 /** 92 * Ported from skins/common/edit.js by Trevor Parscal 93 * (c) 2009 Wikimedia Foundation (GPLv2) - http://www.wikimedia.org 94 * 95 * Inserts text at the beginning and end of a text selection, optionally 96 * inserting text at the caret when selection is empty. 97 * 98 * @fixme document the options parameters 99 */ 100 encapsulateSelection: function ( options ) { 101 return this.each( function () { 102 var selText, scrollTop, insertText, 103 isSample, range, range2, range3, startPos, endPos, 104 pre = options.pre, 105 post = options.post; 106 107 /** 108 * Check if the selected text is the same as the insert text 109 */ 110 function checkSelectedText() { 111 if ( !selText ) { 112 selText = options.peri; 113 isSample = true; 114 } else if ( options.replace ) { 115 selText = options.peri; 116 } else { 117 while ( selText.charAt( selText.length - 1 ) === ' ' ) { 118 // Exclude ending space char 119 selText = selText.slice( 0, -1 ); 120 post += ' '; 121 } 122 while ( selText.charAt( 0 ) === ' ' ) { 123 // Exclude prepending space char 124 selText = selText.slice( 1 ); 125 pre = ' ' + pre; 126 } 127 } 128 } 129 130 /** 131 * Do the splitlines stuff. 132 * 133 * Wrap each line of the selected text with pre and post 134 */ 135 function doSplitLines( selText, pre, post ) { 136 var i, 137 insertText = '', 138 selTextArr = selText.split( '\n' ); 139 for ( i = 0; i < selTextArr.length; i++ ) { 140 insertText += pre + selTextArr[i] + post; 141 if ( i !== selTextArr.length - 1 ) { 142 insertText += '\n'; 143 } 144 } 145 return insertText; 146 } 147 148 isSample = false; 149 // Do nothing if display none 150 if ( this.style.display !== 'none' ) { 151 if ( document.selection && document.selection.createRange ) { 152 // IE 153 154 // Note that IE9 will trigger the next section unless we check this first. 155 // See bug 35201. 156 157 activateElementOnIE( this ); 158 if ( context ) { 159 context.fn.restoreCursorAndScrollTop(); 160 } 161 if ( options.selectionStart !== undefined ) { 162 $( this ).textSelection( 'setSelection', { 'start': options.selectionStart, 'end': options.selectionEnd } ); 163 } 164 165 selText = $( this ).textSelection( 'getSelection' ); 166 scrollTop = this.scrollTop; 167 range = document.selection.createRange(); 168 169 checkSelectedText(); 170 insertText = pre + selText + post; 171 if ( options.splitlines ) { 172 insertText = doSplitLines( selText, pre, post ); 173 } 174 if ( options.ownline && range.moveStart ) { 175 range2 = document.selection.createRange(); 176 range2.collapse(); 177 range2.moveStart( 'character', -1 ); 178 // FIXME: Which check is correct? 179 if ( range2.text !== '\r' && range2.text !== '\n' && range2.text !== '' ) { 180 insertText = '\n' + insertText; 181 pre += '\n'; 182 } 183 range3 = document.selection.createRange(); 184 range3.collapse( false ); 185 range3.moveEnd( 'character', 1 ); 186 if ( range3.text !== '\r' && range3.text !== '\n' && range3.text !== '' ) { 187 insertText += '\n'; 188 post += '\n'; 189 } 190 } 191 192 range.text = insertText; 193 if ( isSample && options.selectPeri && range.moveStart ) { 194 range.moveStart( 'character', -post.length - selText.length ); 195 range.moveEnd( 'character', -post.length ); 196 } 197 range.select(); 198 // Restore the scroll position 199 this.scrollTop = scrollTop; 200 } else if ( this.selectionStart || this.selectionStart === 0 ) { 201 // Mozilla/Opera 202 203 $( this ).focus(); 204 if ( options.selectionStart !== undefined ) { 205 $( this ).textSelection( 'setSelection', { 'start': options.selectionStart, 'end': options.selectionEnd } ); 206 } 207 208 selText = $( this ).textSelection( 'getSelection' ); 209 startPos = this.selectionStart; 210 endPos = this.selectionEnd; 211 scrollTop = this.scrollTop; 212 checkSelectedText(); 213 if ( options.selectionStart !== undefined 214 && endPos - startPos !== options.selectionEnd - options.selectionStart ) 215 { 216 // This means there is a difference in the selection range returned by browser and what we passed. 217 // This happens for Chrome in the case of composite characters. Ref bug #30130 218 // Set the startPos to the correct position. 219 startPos = options.selectionStart; 220 } 221 222 insertText = pre + selText + post; 223 if ( options.splitlines ) { 224 insertText = doSplitLines( selText, pre, post ); 225 } 226 if ( options.ownline ) { 227 if ( startPos !== 0 && this.value.charAt( startPos - 1 ) !== '\n' && this.value.charAt( startPos - 1 ) !== '\r' ) { 228 insertText = '\n' + insertText; 229 pre += '\n'; 230 } 231 if ( this.value.charAt( endPos ) !== '\n' && this.value.charAt( endPos ) !== '\r' ) { 232 insertText += '\n'; 233 post += '\n'; 234 } 235 } 236 this.value = this.value.slice( 0, startPos ) + insertText + 237 this.value.slice( endPos ); 238 // Setting this.value scrolls the textarea to the top, restore the scroll position 239 this.scrollTop = scrollTop; 240 if ( window.opera ) { 241 pre = pre.replace( /\r?\n/g, '\r\n' ); 242 selText = selText.replace( /\r?\n/g, '\r\n' ); 243 post = post.replace( /\r?\n/g, '\r\n' ); 244 } 245 if ( isSample && options.selectPeri && !options.splitlines ) { 246 this.selectionStart = startPos + pre.length; 247 this.selectionEnd = startPos + pre.length + selText.length; 248 } else { 249 this.selectionStart = startPos + insertText.length; 250 this.selectionEnd = this.selectionStart; 251 } 252 } 253 } 254 $( this ).trigger( 'encapsulateSelection', [ options.pre, options.peri, options.post, options.ownline, 255 options.replace, options.spitlines ] ); 256 } ); 257 }, 258 /** 259 * Ported from Wikia's LinkSuggest extension 260 * https://svn.wikia-code.com/wikia/trunk/extensions/wikia/LinkSuggest 261 * Some code copied from 262 * http://www.dedestruct.com/2008/03/22/howto-cross-browser-cursor-position-in-textareas/ 263 * 264 * Get the position (in resolution of bytes not necessarily characters) 265 * in a textarea 266 * 267 * Will focus the textarea in some browsers (IE/Opera) 268 * 269 * @fixme document the options parameters 270 */ 271 getCaretPosition: function ( options ) { 272 function getCaret( e ) { 273 var caretPos = 0, 274 endPos = 0, 275 preText, rawPreText, periText, 276 rawPeriText, postText, rawPostText, 277 // IE Support 278 preFinished, 279 periFinished, 280 postFinished, 281 // Range containing text in the selection 282 periRange, 283 // Range containing text before the selection 284 preRange, 285 // Range containing text after the selection 286 postRange; 287 288 if ( e && document.selection && document.selection.createRange ) { 289 // IE doesn't properly report non-selected caret position through 290 // the selection ranges when textarea isn't focused. This can 291 // lead to saving a bogus empty selection, which then screws up 292 // whatever we do later (bug 31847). 293 activateElementOnIE( e ); 294 295 preFinished = false; 296 periFinished = false; 297 postFinished = false; 298 periRange = document.selection.createRange().duplicate(); 299 300 preRange = rangeForElementIE( e ); 301 // Move the end where we need it 302 preRange.setEndPoint( 'EndToStart', periRange ); 303 304 postRange = rangeForElementIE( e ); 305 // Move the start where we need it 306 postRange.setEndPoint( 'StartToEnd', periRange ); 307 308 // Load the text values we need to compare 309 preText = rawPreText = preRange.text; 310 periText = rawPeriText = periRange.text; 311 postText = rawPostText = postRange.text; 312 313 /* 314 * Check each range for trimmed newlines by shrinking the range by 1 315 * character and seeing if the text property has changed. If it has 316 * not changed then we know that IE has trimmed a \r\n from the end. 317 */ 318 do { 319 if ( !preFinished ) { 320 if ( preRange.compareEndPoints( 'StartToEnd', preRange ) === 0 ) { 321 preFinished = true; 322 } else { 323 preRange.moveEnd( 'character', -1 ); 324 if ( preRange.text === preText ) { 325 rawPreText += '\r\n'; 326 } else { 327 preFinished = true; 328 } 329 } 330 } 331 if ( !periFinished ) { 332 if ( periRange.compareEndPoints( 'StartToEnd', periRange ) === 0 ) { 333 periFinished = true; 334 } else { 335 periRange.moveEnd( 'character', -1 ); 336 if ( periRange.text === periText ) { 337 rawPeriText += '\r\n'; 338 } else { 339 periFinished = true; 340 } 341 } 342 } 343 if ( !postFinished ) { 344 if ( postRange.compareEndPoints( 'StartToEnd', postRange ) === 0 ) { 345 postFinished = true; 346 } else { 347 postRange.moveEnd( 'character', -1 ); 348 if ( postRange.text === postText ) { 349 rawPostText += '\r\n'; 350 } else { 351 postFinished = true; 352 } 353 } 354 } 355 } while ( ( !preFinished || !periFinished || !postFinished ) ); 356 caretPos = rawPreText.replace( /\r\n/g, '\n' ).length; 357 endPos = caretPos + rawPeriText.replace( /\r\n/g, '\n' ).length; 358 } else if ( e && ( e.selectionStart || e.selectionStart === 0 ) ) { 359 // Firefox support 360 caretPos = e.selectionStart; 361 endPos = e.selectionEnd; 362 } 363 return options.startAndEnd ? [ caretPos, endPos ] : caretPos; 364 } 365 return getCaret( this.get( 0 ) ); 366 }, 367 /** 368 * @fixme document the options parameters 369 */ 370 setSelection: function ( options ) { 371 return this.each( function () { 372 var selection, length, newLines; 373 // Do nothing if hidden 374 if ( !$( this ).is( ':hidden' ) ) { 375 if ( this.selectionStart || this.selectionStart === 0 ) { 376 // Opera 9.0 doesn't allow setting selectionStart past 377 // selectionEnd; any attempts to do that will be ignored 378 // Make sure to set them in the right order 379 if ( options.start > this.selectionEnd ) { 380 this.selectionEnd = options.end; 381 this.selectionStart = options.start; 382 } else { 383 this.selectionStart = options.start; 384 this.selectionEnd = options.end; 385 } 386 } else if ( document.body.createTextRange ) { 387 selection = rangeForElementIE( this ); 388 length = this.value.length; 389 // IE doesn't count \n when computing the offset, so we won't either 390 newLines = this.value.match( /\n/g ); 391 if ( newLines ) { 392 length = length - newLines.length; 393 } 394 selection.moveStart( 'character', options.start ); 395 selection.moveEnd( 'character', -length + options.end ); 396 397 // This line can cause an error under certain circumstances (textarea empty, no selection) 398 // Silence that error 399 try { 400 selection.select(); 401 } catch ( e ) { } 402 } 403 } 404 } ); 405 }, 406 /** 407 * Ported from Wikia's LinkSuggest extension 408 * https://svn.wikia-code.com/wikia/trunk/extensions/wikia/LinkSuggest 409 * 410 * Scroll a textarea to the current cursor position. You can set the cursor 411 * position with setSelection() 412 * @param options boolean Whether to force a scroll even if the caret position 413 * is already visible. Defaults to false 414 * 415 * @fixme document the options parameters (function body suggests options.force is a boolean, not options itself) 416 */ 417 scrollToCaretPosition: function ( options ) { 418 function getLineLength( e ) { 419 return Math.floor( e.scrollWidth / ( $.client.profile().platform === 'linux' ? 7 : 8 ) ); 420 } 421 function getCaretScrollPosition( e ) { 422 // FIXME: This functions sucks and is off by a few lines most 423 // of the time. It should be replaced by something decent. 424 var i, j, 425 nextSpace, 426 text = e.value.replace( /\r/g, '' ), 427 caret = $( e ).textSelection( 'getCaretPosition' ), 428 lineLength = getLineLength( e ), 429 row = 0, 430 charInLine = 0, 431 lastSpaceInLine = 0; 432 433 for ( i = 0; i < caret; i++ ) { 434 charInLine++; 435 if ( text.charAt( i ) === ' ' ) { 436 lastSpaceInLine = charInLine; 437 } else if ( text.charAt( i ) === '\n' ) { 438 lastSpaceInLine = 0; 439 charInLine = 0; 440 row++; 441 } 442 if ( charInLine > lineLength ) { 443 if ( lastSpaceInLine > 0 ) { 444 charInLine = charInLine - lastSpaceInLine; 445 lastSpaceInLine = 0; 446 row++; 447 } 448 } 449 } 450 nextSpace = 0; 451 for ( j = caret; j < caret + lineLength; j++ ) { 452 if ( 453 text.charAt( j ) === ' ' || 454 text.charAt( j ) === '\n' || 455 caret === text.length 456 ) { 457 nextSpace = j; 458 break; 459 } 460 } 461 if ( nextSpace > lineLength && caret <= lineLength ) { 462 charInLine = caret - lastSpaceInLine; 463 row++; 464 } 465 return ( $.client.profile().platform === 'mac' ? 13 : ( $.client.profile().platform === 'linux' ? 15 : 16 ) ) * row; 466 } 467 return this.each( function () { 468 var scroll, range, savedRange, pos, oldScrollTop; 469 // Do nothing if hidden 470 if ( !$( this ).is( ':hidden' ) ) { 471 if ( this.selectionStart || this.selectionStart === 0 ) { 472 // Mozilla 473 scroll = getCaretScrollPosition( this ); 474 if ( options.force || scroll < $( this ).scrollTop() || 475 scroll > $( this ).scrollTop() + $( this ).height() ) { 476 $( this ).scrollTop( scroll ); 477 } 478 } else if ( document.selection && document.selection.createRange ) { 479 // IE / Opera 480 /* 481 * IE automatically scrolls the selected text to the 482 * bottom of the textarea at range.select() time, except 483 * if it was already in view and the cursor position 484 * wasn't changed, in which case it does nothing. To 485 * cover that case, we'll force it to act by moving one 486 * character back and forth. 487 */ 488 range = document.body.createTextRange(); 489 savedRange = document.selection.createRange(); 490 pos = $( this ).textSelection( 'getCaretPosition' ); 491 oldScrollTop = this.scrollTop; 492 range.moveToElementText( this ); 493 range.collapse(); 494 range.move( 'character', pos + 1 ); 495 range.select(); 496 if ( this.scrollTop !== oldScrollTop ) { 497 this.scrollTop += range.offsetTop; 498 } else if ( options.force ) { 499 range.move( 'character', -1 ); 500 range.select(); 501 } 502 savedRange.select(); 503 } 504 } 505 $( this ).trigger( 'scrollToPosition' ); 506 } ); 507 } 508 }; 509 510 // Apply defaults 511 switch ( command ) { 512 //case 'getContents': // no params 513 //case 'setContents': // no params with defaults 514 //case 'getSelection': // no params 515 case 'encapsulateSelection': 516 options = $.extend( { 517 pre: '', // Text to insert before the cursor/selection 518 peri: '', // Text to insert between pre and post and select afterwards 519 post: '', // Text to insert after the cursor/selection 520 ownline: false, // Put the inserted text on a line of its own 521 replace: false, // If there is a selection, replace it with peri instead of leaving it alone 522 selectPeri: true, // Select the peri text if it was inserted (but not if there was a selection and replace==false, or if splitlines==true) 523 splitlines: false, // If multiple lines are selected, encapsulate each line individually 524 selectionStart: undefined, // Position to start selection at 525 selectionEnd: undefined // Position to end selection at. Defaults to start 526 }, options ); 527 break; 528 case 'getCaretPosition': 529 options = $.extend( { 530 // Return [start, end] instead of just start 531 startAndEnd: false 532 }, options ); 533 // FIXME: We may not need character position-based functions if we insert markers in the right places 534 break; 535 case 'setSelection': 536 options = $.extend( { 537 // Position to start selection at 538 start: undefined, 539 // Position to end selection at. Defaults to start 540 end: undefined 541 }, options ); 542 543 if ( options.end === undefined ) { 544 options.end = options.start; 545 } 546 // FIXME: We may not need character position-based functions if we insert markers in the right places 547 break; 548 case 'scrollToCaretPosition': 549 options = $.extend( { 550 force: false // Force a scroll even if the caret position is already visible 551 }, options ); 552 break; 553 } 554 555 context = $( this ).data( 'wikiEditor-context' ); 556 hasWikiEditorSurface = ( context !== undefined && context.$iframe !== undefined ); 557 558 // IE selection restore voodoo 559 needSave = false; 560 if ( hasWikiEditorSurface && context.savedSelection !== null ) { 561 context.fn.restoreSelection(); 562 needSave = true; 563 } 564 retval = ( hasWikiEditorSurface && context.fn[command] !== undefined ? context.fn : fn )[command].call( this, options ); 565 if ( hasWikiEditorSurface && needSave ) { 566 context.fn.saveSelection(); 567 } 568 569 return retval; 570 }; 571 572 }( jQuery ) );
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |