Index: trunk/extensions/UsabilityInitiative/UsabilityInitiative.hooks.php |
— | — | @@ -105,7 +105,7 @@ |
106 | 106 | array( |
107 | 107 | 'src' => 'js/js2stopgap/jquery.textSelection.js', |
108 | 108 | 'class' => 'j.fn.textSelection', |
109 | | - 'version' => 23 |
| 109 | + 'version' => 24 |
110 | 110 | ), |
111 | 111 | |
112 | 112 | // Core functionality of extension scripts |
— | — | @@ -152,7 +152,7 @@ |
153 | 153 | array( |
154 | 154 | 'src' => 'js/plugins/jquery.wikiEditor.js', |
155 | 155 | 'class' => 'j.wikiEditor', |
156 | | - 'version' => 59 |
| 156 | + 'version' => 60 |
157 | 157 | ), |
158 | 158 | array( |
159 | 159 | 'src' => 'js/plugins/jquery.wikiEditor.highlight.js', |
— | — | @@ -190,10 +190,10 @@ |
191 | 191 | 'version' => 1 ), |
192 | 192 | ), |
193 | 193 | 'combined' => array( |
194 | | - array( 'src' => 'js/plugins.combined.js', 'version' => 166 ), |
| 194 | + array( 'src' => 'js/plugins.combined.js', 'version' => 167 ), |
195 | 195 | ), |
196 | 196 | 'minified' => array( |
197 | | - array( 'src' => 'js/plugins.combined.min.js', 'version' => 166 ), |
| 197 | + array( 'src' => 'js/plugins.combined.min.js', 'version' => 167 ), |
198 | 198 | ), |
199 | 199 | ), |
200 | 200 | ); |
Index: trunk/extensions/UsabilityInitiative/WikiEditor/WikiEditor.hooks.php |
— | — | @@ -35,7 +35,7 @@ |
36 | 36 | array( |
37 | 37 | 'src' => 'Modules/Toolbar/Toolbar.js', |
38 | 38 | 'class' => 'wikiEditor.config.toolbar', |
39 | | - 'version' => 27 |
| 39 | + 'version' => 28 |
40 | 40 | ), |
41 | 41 | array( |
42 | 42 | 'src' => 'Modules/TemplateEditor/TemplateEditor.js', |
— | — | @@ -44,10 +44,10 @@ |
45 | 45 | ), |
46 | 46 | ), |
47 | 47 | 'combined' => array( |
48 | | - array( 'src' => 'WikiEditor.combined.js', 'version' => 29 ), |
| 48 | + array( 'src' => 'WikiEditor.combined.js', 'version' => 30 ), |
49 | 49 | ), |
50 | 50 | 'minified' => array( |
51 | | - array( 'src' => 'WikiEditor.combined.min.js', 'version' => 29 ), |
| 51 | + array( 'src' => 'WikiEditor.combined.min.js', 'version' => 30 ), |
52 | 52 | ), |
53 | 53 | ); |
54 | 54 | static $messages = array( |
Index: trunk/extensions/UsabilityInitiative/WikiEditor/WikiEditor.combined.js |
— | — | @@ -1784,6 +1784,8 @@ |
1785 | 1785 | }); |
1786 | 1786 | |
1787 | 1787 | // 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 |
1788 | 1790 | $j(this).data( 'replaceCallback', function( mode ) { |
1789 | 1791 | $j( '#wikieditor-toolbar-replace-nomatch, #wikieditor-toolbar-replace-success, #wikieditor-toolbar-replace-emptysearch, #wikieditor-toolbar-replace-invalidregex' ).hide(); |
1790 | 1792 | var searchStr = $j( '#wikieditor-toolbar-replace-search' ).val(); |
— | — | @@ -1814,45 +1816,57 @@ |
1815 | 1817 | return; |
1816 | 1818 | } |
1817 | 1819 | 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 ) { |
1823 | 1829 | // Search hit BOTTOM, continuing at TOP |
1824 | | - matches = text.match( regex ); |
| 1830 | + offset = 0; |
| 1831 | + s = text; |
| 1832 | + match = s.match( regex ); |
| 1833 | + } |
1825 | 1834 | |
1826 | | - if ( !matches ) |
| 1835 | + if ( !match ) { |
1827 | 1836 | $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 | + } |
1844 | 1862 | $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 ) ) |
1846 | 1864 | .show(); |
1847 | 1865 | $j(this).data( 'offset', 0 ); |
1848 | 1866 | } 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; |
1855 | 1869 | var newEnd = start + replaceStr.length; |
1856 | | - $textarea.focus().textSelection( 'setSelection', { 'start': start, |
| 1870 | + $textarea.textSelection( 'setSelection', { 'start': start, |
1857 | 1871 | 'end': end } ); |
1858 | 1872 | if ( mode == 'replace' ) { |
1859 | 1873 | $textarea |
Index: trunk/extensions/UsabilityInitiative/WikiEditor/WikiEditor.combined.min.js |
— | — | @@ -163,11 +163,11 @@ |
164 | 164 | if(mode=='replaceAll'){flags+='g';} |
165 | 165 | if(!isRegex){searchStr=RegExp.escape(searchStr);} |
166 | 166 | 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});} |
172 | 172 | $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);});} |
173 | 173 | 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;} |
174 | 174 | 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 @@ |
1704 | 1704 | }); |
1705 | 1705 | |
1706 | 1706 | // 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 |
1707 | 1709 | $j(this).data( 'replaceCallback', function( mode ) { |
1708 | 1710 | $j( '#wikieditor-toolbar-replace-nomatch, #wikieditor-toolbar-replace-success, #wikieditor-toolbar-replace-emptysearch, #wikieditor-toolbar-replace-invalidregex' ).hide(); |
1709 | 1711 | var searchStr = $j( '#wikieditor-toolbar-replace-search' ).val(); |
— | — | @@ -1733,45 +1735,57 @@ |
1734 | 1736 | return; |
1735 | 1737 | } |
1736 | 1738 | 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 ) { |
1742 | 1748 | // Search hit BOTTOM, continuing at TOP |
1743 | | - matches = text.match( regex ); |
| 1749 | + offset = 0; |
| 1750 | + s = text; |
| 1751 | + match = s.match( regex ); |
| 1752 | + } |
1744 | 1753 | |
1745 | | - if ( !matches ) |
| 1754 | + if ( !match ) { |
1746 | 1755 | $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 | + } |
1763 | 1781 | $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 ) ) |
1765 | 1783 | .show(); |
1766 | 1784 | $j(this).data( 'offset', 0 ); |
1767 | 1785 | } 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; |
1774 | 1788 | var newEnd = start + replaceStr.length; |
1775 | | - $textarea.focus().textSelection( 'setSelection', { 'start': start, |
| 1789 | + $textarea.textSelection( 'setSelection', { 'start': start, |
1776 | 1790 | 'end': end } ); |
1777 | 1791 | if ( mode == 'replace' ) { |
1778 | 1792 | $textarea |
Index: trunk/extensions/UsabilityInitiative/js/plugins/jquery.wikiEditor.js |
— | — | @@ -509,15 +509,51 @@ |
510 | 510 | * |
511 | 511 | * @param start Character offset of selection start |
512 | 512 | * @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 |
515 | 515 | */ |
516 | 516 | 'setSelection': function( options ) { |
517 | 517 | 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; |
520 | 520 | if ( context.$iframe[0].contentWindow.getSelection ) { |
521 | 521 | // 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 | + |
522 | 558 | var sel = context.$iframe[0].contentWindow.getSelection(); |
523 | 559 | while ( sc.firstChild && sc.nodeName != '#text' ) { |
524 | 560 | sc = sc.firstChild; |
— | — | @@ -526,18 +562,22 @@ |
527 | 563 | ec = ec.firstChild; |
528 | 564 | } |
529 | 565 | 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 ); |
532 | 568 | sel.removeAllRanges(); |
533 | 569 | sel.addRange( range ); |
534 | 570 | context.$iframe[0].contentWindow.focus(); |
535 | 571 | } else if ( context.$iframe[0].contentWindow.document.body.createTextRange ) { |
536 | 572 | // IE |
537 | 573 | var range = context.$iframe[0].contentWindow.document.body.createTextRange(); |
538 | | - range.moveToElementText( sc ); |
| 574 | + if ( sc ) { |
| 575 | + range.moveToElementText( sc ); |
| 576 | + } |
539 | 577 | range.moveStart( 'character', options.start ); |
540 | 578 | var range2 = context.$iframe[0].contentWindow.document.body.createTextRange(); |
541 | | - range2.moveToElementText( ec ); |
| 579 | + if ( ec ) { |
| 580 | + range2.moveToElementText( ec ); |
| 581 | + } |
542 | 582 | range2.collapse(); |
543 | 583 | range2.moveEnd( 'character', options.end ); |
544 | 584 | range.setEndPoint( 'EndToEnd', range2 ); |
— | — | @@ -576,6 +616,10 @@ |
577 | 617 | } |
578 | 618 | $element.trigger( 'scrollToTop' ); |
579 | 619 | }, |
| 620 | + /* |
| 621 | + * End of wonky textSelection "compatible" section that needs attention. |
| 622 | + */ |
| 623 | + |
580 | 624 | /** |
581 | 625 | * Get the first element before the selection matching a certain selector. |
582 | 626 | * @param selector Selector to match. Defaults to '*' |
— | — | @@ -714,10 +758,6 @@ |
715 | 759 | } |
716 | 760 | return new Traverser( start ); |
717 | 761 | } |
718 | | - |
719 | | - /* |
720 | | - * End of wonky textSelection "compatible" section that needs attention. |
721 | | - */ |
722 | 762 | }; |
723 | 763 | |
724 | 764 | /* |
Index: trunk/extensions/UsabilityInitiative/js/plugins.combined.js |
— | — | @@ -6476,7 +6476,7 @@ |
6477 | 6477 | var hasIframe = context !== undefined && context.$iframe !== undefined; |
6478 | 6478 | // iframe functions have not been implemented yet, this is a temp hack |
6479 | 6479 | //var hasIframe = false; |
6480 | | - return ( hasIframe ? context.fn : fn )[command].call( this, options ); |
| 6480 | + return ( hasIframe ? context.fn : fn )[command].call( this, options ) || $(this); |
6481 | 6481 | }; |
6482 | 6482 | |
6483 | 6483 | } )( jQuery );/** |
— | — | @@ -6990,15 +6990,51 @@ |
6991 | 6991 | * |
6992 | 6992 | * @param start Character offset of selection start |
6993 | 6993 | * @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 |
6996 | 6996 | */ |
6997 | 6997 | 'setSelection': function( options ) { |
6998 | 6998 | 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; |
7001 | 7001 | if ( context.$iframe[0].contentWindow.getSelection ) { |
7002 | 7002 | // 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 | + |
7003 | 7039 | var sel = context.$iframe[0].contentWindow.getSelection(); |
7004 | 7040 | while ( sc.firstChild && sc.nodeName != '#text' ) { |
7005 | 7041 | sc = sc.firstChild; |
— | — | @@ -7007,18 +7043,22 @@ |
7008 | 7044 | ec = ec.firstChild; |
7009 | 7045 | } |
7010 | 7046 | 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 ); |
7013 | 7049 | sel.removeAllRanges(); |
7014 | 7050 | sel.addRange( range ); |
7015 | 7051 | context.$iframe[0].contentWindow.focus(); |
7016 | 7052 | } else if ( context.$iframe[0].contentWindow.document.body.createTextRange ) { |
7017 | 7053 | // IE |
7018 | 7054 | var range = context.$iframe[0].contentWindow.document.body.createTextRange(); |
7019 | | - range.moveToElementText( sc ); |
| 7055 | + if ( sc ) { |
| 7056 | + range.moveToElementText( sc ); |
| 7057 | + } |
7020 | 7058 | range.moveStart( 'character', options.start ); |
7021 | 7059 | var range2 = context.$iframe[0].contentWindow.document.body.createTextRange(); |
7022 | | - range2.moveToElementText( ec ); |
| 7060 | + if ( ec ) { |
| 7061 | + range2.moveToElementText( ec ); |
| 7062 | + } |
7023 | 7063 | range2.collapse(); |
7024 | 7064 | range2.moveEnd( 'character', options.end ); |
7025 | 7065 | range.setEndPoint( 'EndToEnd', range2 ); |
— | — | @@ -7057,6 +7097,10 @@ |
7058 | 7098 | } |
7059 | 7099 | $element.trigger( 'scrollToTop' ); |
7060 | 7100 | }, |
| 7101 | + /* |
| 7102 | + * End of wonky textSelection "compatible" section that needs attention. |
| 7103 | + */ |
| 7104 | + |
7061 | 7105 | /** |
7062 | 7106 | * Get the first element before the selection matching a certain selector. |
7063 | 7107 | * @param selector Selector to match. Defaults to '*' |
— | — | @@ -7195,10 +7239,6 @@ |
7196 | 7240 | } |
7197 | 7241 | return new Traverser( start ); |
7198 | 7242 | } |
7199 | | - |
7200 | | - /* |
7201 | | - * End of wonky textSelection "compatible" section that needs attention. |
7202 | | - */ |
7203 | 7243 | }; |
7204 | 7244 | |
7205 | 7245 | /* |
Index: trunk/extensions/UsabilityInitiative/js/js2stopgap/jquery.textSelection.js |
— | — | @@ -370,7 +370,7 @@ |
371 | 371 | var hasIframe = context !== undefined && context.$iframe !== undefined; |
372 | 372 | // iframe functions have not been implemented yet, this is a temp hack |
373 | 373 | //var hasIframe = false; |
374 | | - return ( hasIframe ? context.fn : fn )[command].call( this, options ); |
| 374 | + return ( hasIframe ? context.fn : fn )[command].call( this, options ) || $(this); |
375 | 375 | }; |
376 | 376 | |
377 | 377 | } )( jQuery ); |
\ No newline at end of file |
Index: trunk/extensions/UsabilityInitiative/js/plugins.combined.min.js |
— | — | @@ -433,7 +433,7 @@ |
434 | 434 | $(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) |
435 | 435 | options.end=options.start;if(options.endContainer==undefined) |
436 | 436 | 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;} |
438 | 438 | if(!($.browser.name in $.wikiEditor.browsers[$('body').is('.rtl')?'rtl':'ltr'])){return $.wikiEditor.supported=true;} |
439 | 439 | 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;}}} |
440 | 440 | 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 @@ |
459 | 459 | 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;} |
460 | 460 | 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";}} |
461 | 461 | range.pasteHTML((pre+selText+post).replace(/\</g,'<').replace(/>/g,'>').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;} |
463 | 469 | 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;} |
465 | 473 | 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);} |
466 | 474 | $element.trigger('scrollToTop');},'beforeSelection':function(selector,strict){if(typeof selector=='undefined'){selector='*';} |
467 | 475 | 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 $([]);} |