r61464 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r61463‎ | r61464 | r61465 >
Date:15:20, 24 January 2010
Author:catrope
Status:deferred
Tags:
Comment:
UsabilityInitiative: (bug 22210) Fix search and replace dialog by letting setSelection() accept character offsets and reimplementing the Replace All functionality
Modified paths:
  • /trunk/extensions/UsabilityInitiative/UsabilityInitiative.hooks.php (modified) (history)
  • /trunk/extensions/UsabilityInitiative/WikiEditor/Modules/Toolbar/Toolbar.js (modified) (history)
  • /trunk/extensions/UsabilityInitiative/WikiEditor/WikiEditor.combined.js (modified) (history)
  • /trunk/extensions/UsabilityInitiative/WikiEditor/WikiEditor.combined.min.js (modified) (history)
  • /trunk/extensions/UsabilityInitiative/WikiEditor/WikiEditor.hooks.php (modified) (history)
  • /trunk/extensions/UsabilityInitiative/js/js2stopgap/jquery.textSelection.js (modified) (history)
  • /trunk/extensions/UsabilityInitiative/js/plugins.combined.js (modified) (history)
  • /trunk/extensions/UsabilityInitiative/js/plugins.combined.min.js (modified) (history)
  • /trunk/extensions/UsabilityInitiative/js/plugins/jquery.wikiEditor.js (modified) (history)

Diff [purge]

Index: trunk/extensions/UsabilityInitiative/UsabilityInitiative.hooks.php
@@ -105,7 +105,7 @@
106106 array(
107107 'src' => 'js/js2stopgap/jquery.textSelection.js',
108108 'class' => 'j.fn.textSelection',
109 - 'version' => 23
 109+ 'version' => 24
110110 ),
111111
112112 // Core functionality of extension scripts
@@ -152,7 +152,7 @@
153153 array(
154154 'src' => 'js/plugins/jquery.wikiEditor.js',
155155 'class' => 'j.wikiEditor',
156 - 'version' => 59
 156+ 'version' => 60
157157 ),
158158 array(
159159 'src' => 'js/plugins/jquery.wikiEditor.highlight.js',
@@ -190,10 +190,10 @@
191191 'version' => 1 ),
192192 ),
193193 'combined' => array(
194 - array( 'src' => 'js/plugins.combined.js', 'version' => 166 ),
 194+ array( 'src' => 'js/plugins.combined.js', 'version' => 167 ),
195195 ),
196196 'minified' => array(
197 - array( 'src' => 'js/plugins.combined.min.js', 'version' => 166 ),
 197+ array( 'src' => 'js/plugins.combined.min.js', 'version' => 167 ),
198198 ),
199199 ),
200200 );
Index: trunk/extensions/UsabilityInitiative/WikiEditor/WikiEditor.hooks.php
@@ -35,7 +35,7 @@
3636 array(
3737 'src' => 'Modules/Toolbar/Toolbar.js',
3838 'class' => 'wikiEditor.config.toolbar',
39 - 'version' => 27
 39+ 'version' => 28
4040 ),
4141 array(
4242 'src' => 'Modules/TemplateEditor/TemplateEditor.js',
@@ -44,10 +44,10 @@
4545 ),
4646 ),
4747 'combined' => array(
48 - array( 'src' => 'WikiEditor.combined.js', 'version' => 29 ),
 48+ array( 'src' => 'WikiEditor.combined.js', 'version' => 30 ),
4949 ),
5050 'minified' => array(
51 - array( 'src' => 'WikiEditor.combined.min.js', 'version' => 29 ),
 51+ array( 'src' => 'WikiEditor.combined.min.js', 'version' => 30 ),
5252 ),
5353 );
5454 static $messages = array(
Index: trunk/extensions/UsabilityInitiative/WikiEditor/WikiEditor.combined.js
@@ -1784,6 +1784,8 @@
17851785 });
17861786
17871787 // TODO: Find a cleaner way to share this function
 1788+ // FIXME: This implementation runs in quadratic time on Firefox; setSelection() and other
 1789+ // functions should cache their index->node mapping
17881790 $j(this).data( 'replaceCallback', function( mode ) {
17891791 $j( '#wikieditor-toolbar-replace-nomatch, #wikieditor-toolbar-replace-success, #wikieditor-toolbar-replace-emptysearch, #wikieditor-toolbar-replace-invalidregex' ).hide();
17901792 var searchStr = $j( '#wikieditor-toolbar-replace-search' ).val();
@@ -1814,45 +1816,57 @@
18151817 return;
18161818 }
18171819 var $textarea = $j(this).data( 'context' ).$textarea;
1818 - var text = $j.wikiEditor.fixOperaBrokenness( $textarea.val() );
1819 - var matches = false;
1820 - if ( mode != 'replaceAll' )
1821 - matches = text.substr( $j(this).data( 'offset' ) ).match( regex );
1822 - if ( !matches )
 1820+ var text = $textarea.textSelection( 'getContents' );
 1821+ var match = false;
 1822+ var offset, s;
 1823+ if ( mode != 'replaceAll' ) {
 1824+ offset = $j(this).data( 'offset' );
 1825+ s = text.substr( offset );
 1826+ match = s.match( regex );
 1827+ }
 1828+ if ( !match ) {
18231829 // Search hit BOTTOM, continuing at TOP
1824 - matches = text.match( regex );
 1830+ offset = 0;
 1831+ s = text;
 1832+ match = s.match( regex );
 1833+ }
18251834
1826 - if ( !matches )
 1835+ if ( !match ) {
18271836 $j( '#wikieditor-toolbar-replace-nomatch' ).show();
1828 - else if ( mode == 'replaceAll' ) {
1829 - // Prepare to select the last match
1830 - var start = text.lastIndexOf( matches[matches.length - 1] );
1831 - var end = start + replaceStr.length;
1832 -
1833 - // Calculate how much the last match will move
1834 - var replaced = text.replace( regex, replaceStr );
1835 - var corr = replaced.length - text.length - replaceStr.length + matches[matches.length - 1].length;
1836 - $textarea
1837 - .val( replaced )
1838 - .change()
1839 - .focus()
1840 - .textSelection( 'setSelection', { 'start': start + corr,
1841 - 'end': end + corr } )
1842 - .textSelection( 'scrollToCaretPosition' );
1843 -
 1837+ } else if ( mode == 'replaceAll' ) {
 1838+ // Instead of using repetitive .match() calls, we use one .match() call with /g
 1839+ // and indexOf() followed by substr() to find the offsets. This is actually
 1840+ // faster because our indexOf+substr loop is faster than a match loop, and the
 1841+ // /g match is so ridiculously fast that it's negligible.
 1842+ var index;
 1843+ for ( var i = 0; i < match.length; i++ ) {
 1844+ index = s.indexOf( match[i] );
 1845+ if ( index == -1 ) {
 1846+ // This shouldn't happen
 1847+ break;
 1848+ }
 1849+ s = s.substr( index + match[i].length );
 1850+
 1851+ var start = index + offset;
 1852+ var end = start + match[i].length;
 1853+ var newEnd = start + replaceStr.length;
 1854+ $textarea
 1855+ .textSelection( 'setSelection', { 'start': start, 'end': end } )
 1856+ .textSelection( 'encapsulateSelection', {
 1857+ 'peri': replaceStr,
 1858+ 'replace': true } )
 1859+ .textSelection( 'setSelection', { 'start': start, 'end': newEnd } );
 1860+ offset = newEnd;
 1861+ }
18441862 $j( '#wikieditor-toolbar-replace-success' )
1845 - .text( gM( 'wikieditor-toolbar-tool-replace-success', matches.length ) )
 1863+ .text( gM( 'wikieditor-toolbar-tool-replace-success', match.length ) )
18461864 .show();
18471865 $j(this).data( 'offset', 0 );
18481866 } else {
1849 - var start = text.indexOf( matches[0],
1850 - $j(this).data( 'offset' ) );
1851 - if ( start == -1 )
1852 - // Search hit BOTTOM, continuing at TOP
1853 - start = text.indexOf( matches[0] );
1854 - var end = start + matches[0].length;
 1867+ var start = match.index + offset;
 1868+ var end = start + match[0].length;
18551869 var newEnd = start + replaceStr.length;
1856 - $textarea.focus().textSelection( 'setSelection', { 'start': start,
 1870+ $textarea.textSelection( 'setSelection', { 'start': start,
18571871 'end': end } );
18581872 if ( mode == 'replace' ) {
18591873 $textarea
Index: trunk/extensions/UsabilityInitiative/WikiEditor/WikiEditor.combined.min.js
@@ -163,11 +163,11 @@
164164 if(mode=='replaceAll'){flags+='g';}
165165 if(!isRegex){searchStr=RegExp.escape(searchStr);}
166166 try{var regex=new RegExp(searchStr,flags);}catch(e){$j('#wikieditor-toolbar-replace-invalidregex').text(gM('wikieditor-toolbar-tool-replace-invalidregex',e.message)).show();return;}
167 -var $textarea=$j(this).data('context').$textarea;var text=$j.wikiEditor.fixOperaBrokenness($textarea.val());var matches=false;if(mode!='replaceAll')
168 -matches=text.substr($j(this).data('offset')).match(regex);if(!matches)
169 -matches=text.match(regex);if(!matches)
170 -$j('#wikieditor-toolbar-replace-nomatch').show();else if(mode=='replaceAll'){var start=text.lastIndexOf(matches[matches.length-1]);var end=start+replaceStr.length;var replaced=text.replace(regex,replaceStr);var corr=replaced.length-text.length-replaceStr.length+matches[matches.length-1].length;$textarea.val(replaced).change().focus().textSelection('setSelection',{'start':start+corr,'end':end+corr}).textSelection('scrollToCaretPosition');$j('#wikieditor-toolbar-replace-success').text(gM('wikieditor-toolbar-tool-replace-success',matches.length)).show();$j(this).data('offset',0);}else{var start=text.indexOf(matches[0],$j(this).data('offset'));if(start==-1)
171 -start=text.indexOf(matches[0]);var end=start+matches[0].length;var newEnd=start+replaceStr.length;$textarea.focus().textSelection('setSelection',{'start':start,'end':end});if(mode=='replace'){$textarea.textSelection('encapsulateSelection',{'peri':replaceStr,'replace':true}).textSelection('setSelection',{'start':start,'end':newEnd});}
 167+var $textarea=$j(this).data('context').$textarea;var text=$textarea.textSelection('getContents');var match=false;var offset,s;if(mode!='replaceAll'){offset=$j(this).data('offset');s=text.substr(offset);match=s.match(regex);}
 168+if(!match){offset=0;s=text;match=s.match(regex);}
 169+if(!match){$j('#wikieditor-toolbar-replace-nomatch').show();}else if(mode=='replaceAll'){var index;for(var i=0;i<match.length;i++){index=s.indexOf(match[i]);if(index==-1){break;}
 170+s=s.substr(index+match[i].length);var start=index+offset;var end=start+match[i].length;var newEnd=start+replaceStr.length;$textarea.textSelection('setSelection',{'start':start,'end':end}).textSelection('encapsulateSelection',{'peri':replaceStr,'replace':true}).textSelection('setSelection',{'start':start,'end':newEnd});offset=newEnd;}
 171+$j('#wikieditor-toolbar-replace-success').text(gM('wikieditor-toolbar-tool-replace-success',match.length)).show();$j(this).data('offset',0);}else{var start=match.index+offset;var end=start+match[0].length;var newEnd=start+replaceStr.length;$textarea.textSelection('setSelection',{'start':start,'end':end});if(mode=='replace'){$textarea.textSelection('encapsulateSelection',{'peri':replaceStr,'replace':true}).textSelection('setSelection',{'start':start,'end':newEnd});}
172172 $textarea.textSelection('scrollToCaretPosition');$j(this).data('offset',mode=='replace'?newEnd:end);}});},dialog:{width:500,buttons:{'wikieditor-toolbar-tool-replace-button-findnext':function(e){$j(this).closest('.ui-dialog').data('dialogaction',e.target);$j(this).data('replaceCallback').call(this,'find');},'wikieditor-toolbar-tool-replace-button-replacenext':function(e){$j(this).closest('.ui-dialog').data('dialogaction',e.target);$j(this).data('replaceCallback').call(this,'replace');},'wikieditor-toolbar-tool-replace-button-replaceall':function(e){$j(this).closest('.ui-dialog').data('dialogaction',e.target);$j(this).data('replaceCallback').call(this,'replaceAll');},'wikieditor-toolbar-tool-replace-close':function(){$j(this).dialog('close');}},open:function(){$j(this).data('offset',0);$j('#wikieditor-toolbar-replace-search').focus();$j('#wikieditor-toolbar-replace-nomatch, #wikieditor-toolbar-replace-success, #wikieditor-toolbar-replace-emptysearch, #wikieditor-toolbar-replace-invalidregex').hide();if(!($j(this).data('onetimeonlystuff'))){$j(this).data('onetimeonlystuff',true);$j(this).closest('.ui-dialog').keypress(function(e){if((e.keyCode||e.which)==13){var button=$j(this).data('dialogaction')||$j(this).find('button:first');button.click();e.preventDefault();}});$j(this).closest('.ui-dialog').find('button').focus(function(){$j(this).closest('.ui-dialog').data('dialogaction',this);});}
173173 var dialog=$j(this).closest('.ui-dialog');$j(this).data('context').$textarea.bind('keypress.srdialog',function(e){if((e.keyCode||e.which)==13){var button=dialog.data('dialogaction')||dialog.find('button:first');button.click();e.preventDefault();}});},close:function(){$j(this).data('context').$textarea.unbind('keypress.srdialog').focus();$j(this).closest('.ui-dialog').data('dialogaction',false);}}}}});}});mw.loadDone('wikiEditor.config.toolbar');mw.addMessages({"wikieditor-template-editor-preference":"Enable form-based editing of wiki templates"});mw.ready(function(){if(!wgWikiEditorEnabledModules.templateEditor){return true;}
174174 if($j.fn.wikiEditor){$j('textarea#wpTextbox1').wikiEditor('addModule','templateEditor');}});mw.loadDone('wikiEditor.config.templateEditor');
\ No newline at end of file
Index: trunk/extensions/UsabilityInitiative/WikiEditor/Modules/Toolbar/Toolbar.js
@@ -1703,6 +1703,8 @@
17041704 });
17051705
17061706 // TODO: Find a cleaner way to share this function
 1707+ // FIXME: This implementation runs in quadratic time on Firefox; setSelection() and other
 1708+ // functions should cache their index->node mapping
17071709 $j(this).data( 'replaceCallback', function( mode ) {
17081710 $j( '#wikieditor-toolbar-replace-nomatch, #wikieditor-toolbar-replace-success, #wikieditor-toolbar-replace-emptysearch, #wikieditor-toolbar-replace-invalidregex' ).hide();
17091711 var searchStr = $j( '#wikieditor-toolbar-replace-search' ).val();
@@ -1733,45 +1735,57 @@
17341736 return;
17351737 }
17361738 var $textarea = $j(this).data( 'context' ).$textarea;
1737 - var text = $j.wikiEditor.fixOperaBrokenness( $textarea.val() );
1738 - var matches = false;
1739 - if ( mode != 'replaceAll' )
1740 - matches = text.substr( $j(this).data( 'offset' ) ).match( regex );
1741 - if ( !matches )
 1739+ var text = $textarea.textSelection( 'getContents' );
 1740+ var match = false;
 1741+ var offset, s;
 1742+ if ( mode != 'replaceAll' ) {
 1743+ offset = $j(this).data( 'offset' );
 1744+ s = text.substr( offset );
 1745+ match = s.match( regex );
 1746+ }
 1747+ if ( !match ) {
17421748 // Search hit BOTTOM, continuing at TOP
1743 - matches = text.match( regex );
 1749+ offset = 0;
 1750+ s = text;
 1751+ match = s.match( regex );
 1752+ }
17441753
1745 - if ( !matches )
 1754+ if ( !match ) {
17461755 $j( '#wikieditor-toolbar-replace-nomatch' ).show();
1747 - else if ( mode == 'replaceAll' ) {
1748 - // Prepare to select the last match
1749 - var start = text.lastIndexOf( matches[matches.length - 1] );
1750 - var end = start + replaceStr.length;
1751 -
1752 - // Calculate how much the last match will move
1753 - var replaced = text.replace( regex, replaceStr );
1754 - var corr = replaced.length - text.length - replaceStr.length + matches[matches.length - 1].length;
1755 - $textarea
1756 - .val( replaced )
1757 - .change()
1758 - .focus()
1759 - .textSelection( 'setSelection', { 'start': start + corr,
1760 - 'end': end + corr } )
1761 - .textSelection( 'scrollToCaretPosition' );
1762 -
 1756+ } else if ( mode == 'replaceAll' ) {
 1757+ // Instead of using repetitive .match() calls, we use one .match() call with /g
 1758+ // and indexOf() followed by substr() to find the offsets. This is actually
 1759+ // faster because our indexOf+substr loop is faster than a match loop, and the
 1760+ // /g match is so ridiculously fast that it's negligible.
 1761+ var index;
 1762+ for ( var i = 0; i < match.length; i++ ) {
 1763+ index = s.indexOf( match[i] );
 1764+ if ( index == -1 ) {
 1765+ // This shouldn't happen
 1766+ break;
 1767+ }
 1768+ s = s.substr( index + match[i].length );
 1769+
 1770+ var start = index + offset;
 1771+ var end = start + match[i].length;
 1772+ var newEnd = start + replaceStr.length;
 1773+ $textarea
 1774+ .textSelection( 'setSelection', { 'start': start, 'end': end } )
 1775+ .textSelection( 'encapsulateSelection', {
 1776+ 'peri': replaceStr,
 1777+ 'replace': true } )
 1778+ .textSelection( 'setSelection', { 'start': start, 'end': newEnd } );
 1779+ offset = newEnd;
 1780+ }
17631781 $j( '#wikieditor-toolbar-replace-success' )
1764 - .text( gM( 'wikieditor-toolbar-tool-replace-success', matches.length ) )
 1782+ .text( gM( 'wikieditor-toolbar-tool-replace-success', match.length ) )
17651783 .show();
17661784 $j(this).data( 'offset', 0 );
17671785 } else {
1768 - var start = text.indexOf( matches[0],
1769 - $j(this).data( 'offset' ) );
1770 - if ( start == -1 )
1771 - // Search hit BOTTOM, continuing at TOP
1772 - start = text.indexOf( matches[0] );
1773 - var end = start + matches[0].length;
 1786+ var start = match.index + offset;
 1787+ var end = start + match[0].length;
17741788 var newEnd = start + replaceStr.length;
1775 - $textarea.focus().textSelection( 'setSelection', { 'start': start,
 1789+ $textarea.textSelection( 'setSelection', { 'start': start,
17761790 'end': end } );
17771791 if ( mode == 'replace' ) {
17781792 $textarea
Index: trunk/extensions/UsabilityInitiative/js/plugins/jquery.wikiEditor.js
@@ -509,15 +509,51 @@
510510 *
511511 * @param start Character offset of selection start
512512 * @param end Character offset of selection end
513 - * @param startContainer Element in iframe to start selection in
514 - * @param endContainer Element in iframe to end selection in
 513+ * @param startContainer Element in iframe to start selection in. If not set, start is a character offset
 514+ * @param endContainer Element in iframe to end selection in. If not set, end is a character offset
515515 */
516516 'setSelection': function( options ) {
517517 var sc = options.startContainer, ec = options.endContainer;
518 - sc = sc.jquery ? sc[0] : sc;
519 - ec = ec.jquery ? ec[0] : ec;
 518+ sc = sc && sc.jquery ? sc[0] : sc;
 519+ ec = ec && ec.jquery ? ec[0] : ec;
520520 if ( context.$iframe[0].contentWindow.getSelection ) {
521521 // Firefox and Opera
 522+ var start = options.start, end = options.end;
 523+ if ( !sc || !ec ) {
 524+ // Firefox doesn't support character offsets very well, so use a DOM traversal
 525+ var t = context.fn.traverser( context.$content );
 526+ var startContainer = sc, startOffset = options.start;
 527+ var endContainer = ec, endOffset = options.end;
 528+ var pos = 0;
 529+ while ( t.node ) {
 530+ if ( t.node.nodeName != '#text' && t.node.nodeName != 'BR' ) {
 531+ t.goNext();
 532+ continue;
 533+ }
 534+ var newPos = t.node.nodeName == '#text' ? pos + t.node.nodeValue.length : pos + 1;
 535+ if ( !startContainer && pos <= options.start && options.start < newPos ) {
 536+ startContainer = t.node;
 537+ startOffset = options.start - pos;
 538+ }
 539+ if ( startContainer && pos <= options.end && options.end < newPos ) {
 540+ endContainer = t.node;
 541+ endOffset = options.end - pos;
 542+ break;
 543+ }
 544+ pos = newPos;
 545+ t.goNext();
 546+ }
 547+ sc = startContainer;
 548+ ec = endContainer;
 549+ start = startOffset;
 550+ end = endOffset;
 551+ }
 552+ if ( !sc || !ec ) {
 553+ // The DOM traversal didn't find the requested offset
 554+ // Give up
 555+ return;
 556+ }
 557+
522558 var sel = context.$iframe[0].contentWindow.getSelection();
523559 while ( sc.firstChild && sc.nodeName != '#text' ) {
524560 sc = sc.firstChild;
@@ -526,18 +562,22 @@
527563 ec = ec.firstChild;
528564 }
529565 var range = context.$iframe[0].contentWindow.document.createRange();
530 - range.setStart( sc, options.start );
531 - range.setEnd( ec, options.end );
 566+ range.setStart( sc, start );
 567+ range.setEnd( ec, end );
532568 sel.removeAllRanges();
533569 sel.addRange( range );
534570 context.$iframe[0].contentWindow.focus();
535571 } else if ( context.$iframe[0].contentWindow.document.body.createTextRange ) {
536572 // IE
537573 var range = context.$iframe[0].contentWindow.document.body.createTextRange();
538 - range.moveToElementText( sc );
 574+ if ( sc ) {
 575+ range.moveToElementText( sc );
 576+ }
539577 range.moveStart( 'character', options.start );
540578 var range2 = context.$iframe[0].contentWindow.document.body.createTextRange();
541 - range2.moveToElementText( ec );
 579+ if ( ec ) {
 580+ range2.moveToElementText( ec );
 581+ }
542582 range2.collapse();
543583 range2.moveEnd( 'character', options.end );
544584 range.setEndPoint( 'EndToEnd', range2 );
@@ -576,6 +616,10 @@
577617 }
578618 $element.trigger( 'scrollToTop' );
579619 },
 620+ /*
 621+ * End of wonky textSelection "compatible" section that needs attention.
 622+ */
 623+
580624 /**
581625 * Get the first element before the selection matching a certain selector.
582626 * @param selector Selector to match. Defaults to '*'
@@ -714,10 +758,6 @@
715759 }
716760 return new Traverser( start );
717761 }
718 -
719 - /*
720 - * End of wonky textSelection "compatible" section that needs attention.
721 - */
722762 };
723763
724764 /*
Index: trunk/extensions/UsabilityInitiative/js/plugins.combined.js
@@ -6476,7 +6476,7 @@
64776477 var hasIframe = context !== undefined && context.$iframe !== undefined;
64786478 // iframe functions have not been implemented yet, this is a temp hack
64796479 //var hasIframe = false;
6480 - return ( hasIframe ? context.fn : fn )[command].call( this, options );
 6480+ return ( hasIframe ? context.fn : fn )[command].call( this, options ) || $(this);
64816481 };
64826482
64836483 } )( jQuery );/**
@@ -6990,15 +6990,51 @@
69916991 *
69926992 * @param start Character offset of selection start
69936993 * @param end Character offset of selection end
6994 - * @param startContainer Element in iframe to start selection in
6995 - * @param endContainer Element in iframe to end selection in
 6994+ * @param startContainer Element in iframe to start selection in. If not set, start is a character offset
 6995+ * @param endContainer Element in iframe to end selection in. If not set, end is a character offset
69966996 */
69976997 'setSelection': function( options ) {
69986998 var sc = options.startContainer, ec = options.endContainer;
6999 - sc = sc.jquery ? sc[0] : sc;
7000 - ec = ec.jquery ? ec[0] : ec;
 6999+ sc = sc && sc.jquery ? sc[0] : sc;
 7000+ ec = ec && ec.jquery ? ec[0] : ec;
70017001 if ( context.$iframe[0].contentWindow.getSelection ) {
70027002 // Firefox and Opera
 7003+ var start = options.start, end = options.end;
 7004+ if ( !sc || !ec ) {
 7005+ // Firefox doesn't support character offsets very well, so use a DOM traversal
 7006+ var t = context.fn.traverser( context.$content );
 7007+ var startContainer = sc, startOffset = options.start;
 7008+ var endContainer = ec, endOffset = options.end;
 7009+ var pos = 0;
 7010+ while ( t.node ) {
 7011+ if ( t.node.nodeName != '#text' && t.node.nodeName != 'BR' ) {
 7012+ t.goNext();
 7013+ continue;
 7014+ }
 7015+ var newPos = t.node.nodeName == '#text' ? pos + t.node.nodeValue.length : pos + 1;
 7016+ if ( !startContainer && pos <= options.start && options.start < newPos ) {
 7017+ startContainer = t.node;
 7018+ startOffset = options.start - pos;
 7019+ }
 7020+ if ( startContainer && pos <= options.end && options.end < newPos ) {
 7021+ endContainer = t.node;
 7022+ endOffset = options.end - pos;
 7023+ break;
 7024+ }
 7025+ pos = newPos;
 7026+ t.goNext();
 7027+ }
 7028+ sc = startContainer;
 7029+ ec = endContainer;
 7030+ start = startOffset;
 7031+ end = endOffset;
 7032+ }
 7033+ if ( !sc || !ec ) {
 7034+ // The DOM traversal didn't find the requested offset
 7035+ // Give up
 7036+ return;
 7037+ }
 7038+
70037039 var sel = context.$iframe[0].contentWindow.getSelection();
70047040 while ( sc.firstChild && sc.nodeName != '#text' ) {
70057041 sc = sc.firstChild;
@@ -7007,18 +7043,22 @@
70087044 ec = ec.firstChild;
70097045 }
70107046 var range = context.$iframe[0].contentWindow.document.createRange();
7011 - range.setStart( sc, options.start );
7012 - range.setEnd( ec, options.end );
 7047+ range.setStart( sc, start );
 7048+ range.setEnd( ec, end );
70137049 sel.removeAllRanges();
70147050 sel.addRange( range );
70157051 context.$iframe[0].contentWindow.focus();
70167052 } else if ( context.$iframe[0].contentWindow.document.body.createTextRange ) {
70177053 // IE
70187054 var range = context.$iframe[0].contentWindow.document.body.createTextRange();
7019 - range.moveToElementText( sc );
 7055+ if ( sc ) {
 7056+ range.moveToElementText( sc );
 7057+ }
70207058 range.moveStart( 'character', options.start );
70217059 var range2 = context.$iframe[0].contentWindow.document.body.createTextRange();
7022 - range2.moveToElementText( ec );
 7060+ if ( ec ) {
 7061+ range2.moveToElementText( ec );
 7062+ }
70237063 range2.collapse();
70247064 range2.moveEnd( 'character', options.end );
70257065 range.setEndPoint( 'EndToEnd', range2 );
@@ -7057,6 +7097,10 @@
70587098 }
70597099 $element.trigger( 'scrollToTop' );
70607100 },
 7101+ /*
 7102+ * End of wonky textSelection "compatible" section that needs attention.
 7103+ */
 7104+
70617105 /**
70627106 * Get the first element before the selection matching a certain selector.
70637107 * @param selector Selector to match. Defaults to '*'
@@ -7195,10 +7239,6 @@
71967240 }
71977241 return new Traverser( start );
71987242 }
7199 -
7200 - /*
7201 - * End of wonky textSelection "compatible" section that needs attention.
7202 - */
72037243 };
72047244
72057245 /*
Index: trunk/extensions/UsabilityInitiative/js/js2stopgap/jquery.textSelection.js
@@ -370,7 +370,7 @@
371371 var hasIframe = context !== undefined && context.$iframe !== undefined;
372372 // iframe functions have not been implemented yet, this is a temp hack
373373 //var hasIframe = false;
374 - return ( hasIframe ? context.fn : fn )[command].call( this, options );
 374+ return ( hasIframe ? context.fn : fn )[command].call( this, options ) || $(this);
375375 };
376376
377377 } )( jQuery );
\ No newline at end of file
Index: trunk/extensions/UsabilityInitiative/js/plugins.combined.min.js
@@ -433,7 +433,7 @@
434434 $(this).trigger('scrollToPosition');});}};switch(command){case'encapsulateSelection':options=$.extend({'pre':'','peri':'','post':'','ownline':false,'replace':false},options);break;case'getCaretPosition':options=$.extend({'startAndEnd':false},options);break;case'setSelection':options=$.extend({'start':undefined,'end':undefined,'startContainer':undefined,'endContainer':undefined},options);if(options.end===undefined)
435435 options.end=options.start;if(options.endContainer==undefined)
436436 options.endContainer=options.startContainer;break;case'scrollToCaretPosition':options=$.extend({'force':false},options);break;}
437 -var context=$(this).data('wikiEditor-context');var hasIframe=context!==undefined&&context.$iframe!==undefined;return(hasIframe?context.fn:fn)[command].call(this,options);};})(jQuery);mw.addMessages({"wikieditor":"Advanced wikitext editing interface","wikieditor-desc":"Provides an extendable wikitext editing interface and many feature-providing modules","wikieditor-wikitext-tab":"Wikitext"});(function($){$.wikiEditor={'modules':{},'instances':[],'browsers':{'ltr':{'msie':[['>=',7]],'firefox':[['>=',2],['!=','2.0'],['!=','2.0.0.1'],['!=','2.0.0.2'],['!=','2.0.0.3'],['!=','2.0.0.4']],'opera':[['>=',9.6]],'safari':[['>=',3.1]]},'rtl':{'msie':[['>=',8]],'firefox':[['>=',2],['!=','2.0'],['!=','2.0.0.1'],['!=','2.0.0.2'],['!=','2.0.0.3'],['!=','2.0.0.4']],'opera':[['>=',9.6]],'safari':[['>=',3.1]]}},'imgPath':wgScriptPath+'/extensions/UsabilityInitiative/images/wikiEditor/','isSupported':function(){if(typeof $.wikiEditor.supported!='undefined'){return $.wikiEditor.supported;}
 437+var context=$(this).data('wikiEditor-context');var hasIframe=context!==undefined&&context.$iframe!==undefined;return(hasIframe?context.fn:fn)[command].call(this,options)||$(this);};})(jQuery);mw.addMessages({"wikieditor":"Advanced wikitext editing interface","wikieditor-desc":"Provides an extendable wikitext editing interface and many feature-providing modules","wikieditor-wikitext-tab":"Wikitext"});(function($){$.wikiEditor={'modules':{},'instances':[],'browsers':{'ltr':{'msie':[['>=',7]],'firefox':[['>=',2],['!=','2.0'],['!=','2.0.0.1'],['!=','2.0.0.2'],['!=','2.0.0.3'],['!=','2.0.0.4']],'opera':[['>=',9.6]],'safari':[['>=',3.1]]},'rtl':{'msie':[['>=',8]],'firefox':[['>=',2],['!=','2.0'],['!=','2.0.0.1'],['!=','2.0.0.2'],['!=','2.0.0.3'],['!=','2.0.0.4']],'opera':[['>=',9.6]],'safari':[['>=',3.1]]}},'imgPath':wgScriptPath+'/extensions/UsabilityInitiative/images/wikiEditor/','isSupported':function(){if(typeof $.wikiEditor.supported!='undefined'){return $.wikiEditor.supported;}
438438 if(!($.browser.name in $.wikiEditor.browsers[$('body').is('.rtl')?'rtl':'ltr'])){return $.wikiEditor.supported=true;}
439439 var browser=$.wikiEditor.browsers[$('body').is('.rtl')?'rtl':'ltr'][$.browser.name];for(condition in browser){var op=browser[condition][0];var val=browser[condition][1];if(typeof val=='string'){if(!(eval('$.browser.version'+op+'"'+val+'"'))){return $.wikiEditor.supported=false;}}else if(typeof val=='number'){if(!(eval('$.browser.versionNumber'+op+val))){return $.wikiEditor.supported=false;}}}
440440 return $.wikiEditor.supported=true;},'autoMsg':function(object,property){if(typeof property=='object'){for(i in property){if(property[i]in object||property[i]+'Msg'in object){property=property[i];break;}}}
@@ -458,9 +458,17 @@
459459 if(lastNode){context.fn.scrollToTop(lastNode);}}else if(context.$iframe[0].contentWindow.document.selection){context.$iframe[0].contentWindow.focus();var range=context.$iframe[0].contentWindow.document.selection.createRange();if(options.ownline&&range.moveStart){var range2=context.$iframe[0].contentWindow.document.selection.createRange();range2.collapse();range2.moveStart('character',-1);if(range2.text!="\r"&&range2.text!="\n"&&range2.text!=""){pre="\n"+pre;}
460460 var range3=context.$iframe[0].contentWindow.document.selection.createRange();range3.collapse(false);range3.moveEnd('character',1);if(range3.text!="\r"&&range3.text!="\n"&&range3.text!=""){post+="\n";}}
461461 range.pasteHTML((pre+selText+post).replace(/\</g,'&lt;').replace(/>/g,'&gt;').replace(/\r?\n/g,'<br />'));}
462 -$(context.$iframe[0].contentWindow.document).trigger('encapsulateSelection',[pre,options.peri,post,options.ownline,options.replace]);return context.$textarea;},'getCaretPosition':function(options){},'setSelection':function(options){var sc=options.startContainer,ec=options.endContainer;sc=sc.jquery?sc[0]:sc;ec=ec.jquery?ec[0]:ec;if(context.$iframe[0].contentWindow.getSelection){var sel=context.$iframe[0].contentWindow.getSelection();while(sc.firstChild&&sc.nodeName!='#text'){sc=sc.firstChild;}
 462+$(context.$iframe[0].contentWindow.document).trigger('encapsulateSelection',[pre,options.peri,post,options.ownline,options.replace]);return context.$textarea;},'getCaretPosition':function(options){},'setSelection':function(options){var sc=options.startContainer,ec=options.endContainer;sc=sc&&sc.jquery?sc[0]:sc;ec=ec&&ec.jquery?ec[0]:ec;if(context.$iframe[0].contentWindow.getSelection){var start=options.start,end=options.end;if(!sc||!ec){var t=context.fn.traverser(context.$content);var startContainer=sc,startOffset=options.start;var endContainer=ec,endOffset=options.end;var pos=0;while(t.node){if(t.node.nodeName!='#text'&&t.node.nodeName!='BR'){t.goNext();continue;}
 463+var newPos=t.node.nodeName=='#text'?pos+t.node.nodeValue.length:pos+1;if(!startContainer&&pos<=options.start&&options.start<newPos){startContainer=t.node;startOffset=options.start-pos;}
 464+if(startContainer&&pos<=options.end&&options.end<newPos){endContainer=t.node;endOffset=options.end-pos;break;}
 465+pos=newPos;t.goNext();}
 466+sc=startContainer;ec=endContainer;start=startOffset;end=endOffset;}
 467+if(!sc||!ec){return;}
 468+var sel=context.$iframe[0].contentWindow.getSelection();while(sc.firstChild&&sc.nodeName!='#text'){sc=sc.firstChild;}
463469 while(ec.firstChild&&ec.nodeName!='#text'){ec=ec.firstChild;}
464 -var range=context.$iframe[0].contentWindow.document.createRange();range.setStart(sc,options.start);range.setEnd(ec,options.end);sel.removeAllRanges();sel.addRange(range);context.$iframe[0].contentWindow.focus();}else if(context.$iframe[0].contentWindow.document.body.createTextRange){var range=context.$iframe[0].contentWindow.document.body.createTextRange();range.moveToElementText(sc);range.moveStart('character',options.start);var range2=context.$iframe[0].contentWindow.document.body.createTextRange();range2.moveToElementText(ec);range2.collapse();range2.moveEnd('character',options.end);range.setEndPoint('EndToEnd',range2);range.select();}},'scrollToCaretPosition':function(options){},'scrollToTop':function($element,force){var html=context.$content.closest('html'),body=context.$content.closest('body'),parentHtml=$('html'),parentBody=$('body');var y=$element.offset().top;if(!$.browser.msie){y=parentHtml.scrollTop()>0?y+html.scrollTop()-parentHtml.scrollTop():y;y=parentBody.scrollTop()>0?y+body.scrollTop()-parentBody.scrollTop():y;}
 470+var range=context.$iframe[0].contentWindow.document.createRange();range.setStart(sc,start);range.setEnd(ec,end);sel.removeAllRanges();sel.addRange(range);context.$iframe[0].contentWindow.focus();}else if(context.$iframe[0].contentWindow.document.body.createTextRange){var range=context.$iframe[0].contentWindow.document.body.createTextRange();if(sc){range.moveToElementText(sc);}
 471+range.moveStart('character',options.start);var range2=context.$iframe[0].contentWindow.document.body.createTextRange();if(ec){range2.moveToElementText(ec);}
 472+range2.collapse();range2.moveEnd('character',options.end);range.setEndPoint('EndToEnd',range2);range.select();}},'scrollToCaretPosition':function(options){},'scrollToTop':function($element,force){var html=context.$content.closest('html'),body=context.$content.closest('body'),parentHtml=$('html'),parentBody=$('body');var y=$element.offset().top;if(!$.browser.msie){y=parentHtml.scrollTop()>0?y+html.scrollTop()-parentHtml.scrollTop():y;y=parentBody.scrollTop()>0?y+body.scrollTop()-parentBody.scrollTop():y;}
465473 var topBound=html.scrollTop()>body.scrollTop()?html.scrollTop():body.scrollTop(),bottomBound=topBound+context.$iframe.height();if(force||y<topBound||y>bottomBound){html.scrollTop(y);body.scrollTop(y);}
466474 $element.trigger('scrollToTop');},'beforeSelection':function(selector,strict){if(typeof selector=='undefined'){selector='*';}
467475 var e,offset;if(context.$iframe[0].contentWindow.getSelection){var selection=context.$iframe[0].contentWindow.getSelection();if(selection.baseNode!==null){e=selection.getRangeAt(0).startContainer;offset=selection.startOffset;}else{return $([]);}}else if(context.$iframe[0].contentWindow.document.selection){var range=context.$iframe[0].contentWindow.document.selection.createRange();var range2=context.$iframe[0].contentWindow.document.body.createTextRange();try{range2.setEndPoint('EndToStart',range);}catch(e){return $([]);}

Status & tagging log