r61449 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r61448‎ | r61449 | r61450 >
Date:23:58, 23 January 2010
Author:catrope
Status:deferred
Tags:
Comment:
UsabilityInitiative: Refactored the DOM traversal algorithm into context.fn.traverser() and used this both in highlight.mark() and in the IE branch of context.fn.beforeSelection(). beforeSelection seems to work in IE, but for some reason (event propagation issue?) it doesn't seem to be called
Modified paths:
  • /trunk/extensions/UsabilityInitiative/UsabilityInitiative.hooks.php (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.highlight.js (modified) (history)
  • /trunk/extensions/UsabilityInitiative/js/plugins/jquery.wikiEditor.js (modified) (history)

Diff [purge]

Index: trunk/extensions/UsabilityInitiative/UsabilityInitiative.hooks.php
@@ -152,12 +152,12 @@
153153 array(
154154 'src' => 'js/plugins/jquery.wikiEditor.js',
155155 'class' => 'j.wikiEditor',
156 - 'version' => 58
 156+ 'version' => 59
157157 ),
158158 array(
159159 'src' => 'js/plugins/jquery.wikiEditor.highlight.js',
160160 'class' => 'j.wikiEditor.modules.highlight',
161 - 'version' => 16
 161+ 'version' => 17
162162 ),
163163 array(
164164 'src' => 'js/plugins/jquery.wikiEditor.toolbar.js',
@@ -190,10 +190,10 @@
191191 'version' => 1 ),
192192 ),
193193 'combined' => array(
194 - array( 'src' => 'js/plugins.combined.js', 'version' => 165 ),
 194+ array( 'src' => 'js/plugins.combined.js', 'version' => 166 ),
195195 ),
196196 'minified' => array(
197 - array( 'src' => 'js/plugins.combined.min.js', 'version' => 165 ),
 197+ array( 'src' => 'js/plugins.combined.min.js', 'version' => 166 ),
198198 ),
199199 ),
200200 );
Index: trunk/extensions/UsabilityInitiative/js/plugins/jquery.wikiEditor.js
@@ -491,7 +491,7 @@
492492 );
493493 }
494494 // Trigger the encapsulateSelection event (this might need to get named something else/done differently)
495 - context.$content.trigger(
 495+ $( context.$iframe[0].contentWindow.document ).trigger(
496496 'encapsulateSelection', [ pre, options.peri, post, options.ownline, options.replace ]
497497 );
498498 return context.$textarea;
@@ -586,7 +586,7 @@
587587 if ( typeof selector == 'undefined' ) {
588588 selector = '*';
589589 }
590 - var e;
 590+ var e, offset;
591591 if ( context.$iframe[0].contentWindow.getSelection ) {
592592 // Firefox and Opera
593593 var selection = context.$iframe[0].contentWindow.getSelection();
@@ -595,21 +595,52 @@
596596 // Start at the selection's start and traverse the DOM backwards
597597 // This is done by traversing an element's children first, then the element itself, then its parent
598598 e = selection.getRangeAt( 0 ).startContainer;
 599+ offset = selection.startOffset;
599600 } else {
600601 return $( [] );
601602 }
602603 } else if ( context.$iframe[0].contentWindow.document.selection ) {
603604 // IE
604 - // TODO
605 - return $( [] );
 605+ // Because there's nothing like range.startContainer in IE, we need to do a DOM traversal
 606+ // to find the element the start of the selection is in
 607+ var range = context.$iframe[0].contentWindow.document.selection.createRange();
 608+ // Set range2 to the text before the selection
 609+ var range2 = context.$iframe[0].contentWindow.document.body.createTextRange();
 610+ // For some reason this call throws errors in certain cases, e.g. when the selection is
 611+ // not in the iframe
 612+ try {
 613+ range2.setEndPoint( 'EndToStart', range );
 614+ } catch ( e ) {
 615+ return $( [] );
 616+ }
 617+ var seekPos = range2.text.length;
 618+
 619+ var t = context.fn.traverser( context.$content );
 620+ var pos = 0;
 621+ while ( t.node ) {
 622+ if ( t.node.nodeName != '#text' && t.node.nodeName != 'BR' ) {
 623+ t.goNext();
 624+ continue;
 625+ }
 626+ var newPos = t.node.nodeName == '#text' ? pos + t.node.nodeValue.length : pos + 1;
 627+ if ( pos <= seekPos && seekPos < newPos ) {
 628+ break;
 629+ } else {
 630+ pos = newPos;
 631+ t.goNext();
 632+ }
 633+ }
 634+ e = t.node;
 635+ offset = seekPos - pos;
 636+ if ( !e )
 637+ return $( [] );
606638 }
607639 if ( e.nodeName != '#text' ) {
608640 // The selection is not in a textnode, but between two non-text nodes
609641 // (usually inside the <body> between two <br>s). Go to the rightmost
610642 // child of the node just before the selection
611643 var newE = e.firstChild;
612 - for ( var i = 0; i < selection.startOffset - 1 && newE; i++ ) {
613 - console.log( i );
 644+ for ( var i = 0; i < offset - 1 && newE; i++ ) {
614645 newE = newE.nextSibling;
615646 }
616647 while ( newE && newE.lastChild ) {
@@ -628,6 +659,60 @@
629660 strict = false;
630661 }
631662 return $( [] );
 663+ },
 664+ /**
 665+ * Get an object used to traverse the leaf nodes in the iframe DOM. This traversal skips leaf nodes
 666+ * inside an element with the wikiEditor-noinclude class.
 667+ *
 668+ * Usage:
 669+ * var t = context.fn.traverser( context.$content );
 670+ * // t.node is the first textnode, t.depth is its depth
 671+ * t.goNext();
 672+ * // t.node is the second textnode, t.depth is its depth
 673+ * // Trying to advance past the end will set t.node to null
 674+ */
 675+ 'traverser': function( start ) {
 676+ function Traverser( start ) {
 677+ this.goNext = function() {
 678+ var p = this.node;
 679+ nextDepth = this.depth;
 680+ while ( p && !p.nextSibling ) {
 681+ p = p.parentNode;
 682+ nextDepth--;
 683+ if ( this.depth == 0 ) {
 684+ // We're back at the start node
 685+ p = null;
 686+ }
 687+ }
 688+ p = p ? p.nextSibling : null;
 689+ do {
 690+ // Filter nodes with the wikiEditor-noinclude class
 691+ while ( p && $( p ).hasClass( 'wikiEditor-noinclude' ) ) {
 692+ p = p.nextSibling;
 693+ }
 694+ if ( p && p.firstChild ) {
 695+ p = p.firstChild;
 696+ nextDepth++;
 697+ }
 698+ } while ( p && p.firstChild );
 699+ this.node = p;
 700+ this.depth = nextDepth;
 701+ }
 702+ // Find the leftmost leaf node in the tree
 703+ this.node = start.jquery ? start.get( 0 ) : start;
 704+ this.depth = 0;
 705+ do {
 706+ // Filter nodes with the wikiEditor-noinclude class
 707+ while ( this.node && $( this.node ).hasClass( 'wikiEditor-noinclude' ) ) {
 708+ this.node = this.node.nextSibling;
 709+ }
 710+ if ( this.node && this.node.firstChild ) {
 711+ this.node = this.node.firstChild;
 712+ this.depth++;
 713+ }
 714+ } while ( this.node && this.node.firstChild );
 715+ }
 716+ return new Traverser( start );
632717 }
633718
634719 /*
Index: trunk/extensions/UsabilityInitiative/js/plugins/jquery.wikiEditor.highlight.js
@@ -150,94 +150,62 @@
151151 context.fn.trigger( 'mark' );
152152 markers.sort( function( a, b ) { return a.start - b.start || a.end - b.end; } );
153153
154 - // Traverse the iframe DOM, inserting markers where they're needed. The loop traverses all leaf nodes in the
155 - // DOM, and uses DOM methods rather than jQuery because it has to work with text nodes and for performance.
 154+ // Traverse the iframe DOM, inserting markers where they're needed.
156155 var pos = 0;
157 - var node = context.$content.get( 0 );
158 - var next = null;
 156+ var t = context.fn.traverser( context.$content );
159157 var i = 0; // index for markers[]
160158 var startNode = null;
161 - var depth = 0, nextDepth = 0, startDepth = null;
 159+ var nextDepth = 0, startDepth = null;
162160 var lastTextNode = null, lastTextNodeDepth = null;
163 - // Find the leftmost leaf node in the tree
164 - while ( node.firstChild ) {
165 - node = node.firstChild;
166 - depth++;
167 - // Filter nodes with the wikiEditor-noinclude class
168 - while ( node && $( node ).hasClass( 'wikiEditor-noinclude' ) ) {
169 - node = node.nextSibling;
170 - }
171 - }
172 - while ( i < markers.length && node ) {
173 - // Find the next leaf node
174 - var p = node;
175 - nextDepth = depth;
176 - while ( p && !p.nextSibling ) {
177 - p = p.parentNode;
178 - nextDepth--;
179 - }
180 - // Filter nodes with the wikiEditor-noinclude class
181 - p = p ? p.nextSibling : null;
182 - do {
183 - while ( p && $( p ).hasClass( 'wikiEditor-noinclude' ) ) {
184 - p = p.nextSibling;
185 - }
186 - if ( p && p.firstChild ) {
187 - p = p.firstChild;
188 - nextDepth++;
189 - }
190 - } while ( p && ( $( p ).hasClass( 'wikiEditor-noinclude' ) || p.firstChild ) );
191 -
192 - next = p;
193 - if ( node.nodeName != '#text' && node.nodeName != 'BR' ) {
 161+ while ( i < markers.length && t.node ) {
 162+ if ( t.node.nodeName != '#text' && t.node.nodeName != 'BR' ) {
194163 // Skip this node
195 - node = next;
196 - depth = nextDepth;
 164+ t.goNext();
197165 continue;
198166 }
199 - var newPos = node.nodeName == '#text' ? pos + node.nodeValue.length : pos + 1;
200 - if ( node.nodeName == '#text' ) {
201 - lastTextNode = node;
202 - lastTextNodeDepth = depth;
 167+ var newPos = t.node.nodeName == '#text' ? pos + t.node.nodeValue.length : pos + 1;
 168+ if ( t.node.nodeName == '#text' ) {
 169+ lastTextNode = t.node;
 170+ lastTextNodeDepth = t.depth;
203171 }
204172 // We want to isolate each marker, so we may need to split textNodes
205173 // if a marker starts or end halfway one.
206174 if ( !startNode && markers[i].start >= pos && markers[i].start < newPos ) {
207175 // The next marker starts somewhere in this textNode or at this BR
208176 if ( markers[i].start > pos ) {
209 - // node must be a textnode at this point because
 177+ // t.node must be a textnode at this point because
210178 // start > pos and start < pos+1 can't both be true
211179
212180 // Split off the prefix
213181 // This leaves the prefix in the current node and puts
214182 // the rest in a new node, which we immediately advance to
215 - node = node.splitText( markers[i].start - pos );
 183+ t.node = t.node.splitText( markers[i].start - pos );
216184 pos = markers[i].start;
217185 }
218 - startNode = node;
219 - startDepth = depth;
 186+ startNode = t.node;
 187+ startDepth = t.depth;
220188 }
221189 // Don't wrap BRs, produces undesirable results
222190 if ( startNode && startNode.nodeName == 'BR' ) {
223 - startNode = node;
224 - startDepth = depth;
 191+ startNode = t.node;
 192+ startDepth = t.depth;
225193 }
226194 // TODO: What happens when wrapping a zero-length string?
227195 if ( startNode && markers[i].end > pos && markers[i].end <= newPos ) {
228196 // The marker ends somewhere in this textNode or at this BR
229197 if ( markers[i].end < newPos ) {
230 - // node must be a textnode at this point because
 198+ // t.node must be a textnode at this point because
231199 // end > pos and end < pos+1 can't both be true
232200
233201 // Split off the suffix - This puts the suffix in a new node and leaves the rest in the current
234 - // node. We have to make sure the split-off node will be visited correctly
235 - // node.nodeValue.length - ( newPos - markers[i].end )
236 - next = node.splitText( node.nodeValue.length - newPos + markers[i].end );
 202+ // node.
 203+ // t.node.nodeValue.length - ( newPos - markers[i].end )
 204+ t.node.splitText( t.node.nodeValue.length - newPos + markers[i].end );
237205 newPos = markers[i].end;
238206 }
239207
240208 // Don't wrap leading or trailing BRs, doing that causes weird issues
241 - var endNode = node, endDepth = depth;
 209+ var endNode = t.node, endDepth = t.depth;
242210 if ( endNode.nodeName == 'BR' ) {
243211 endNode = lastTextNode;
244212 endDepth = lastTextNodeDepth;
@@ -249,7 +217,7 @@
250218 // rightmost leaves in the subtrees rooted at ca1 and ca2 respectively; if this is not the case, we
251219 // can't cleanly wrap things without misnesting and we silently fail.
252220 var ca1 = startNode, ca2 = endNode;
253 - // Correct for startNode and node possibly not having the same depth
 221+ // Correct for startNode and endNode possibly not having the same depth
254222 if ( startDepth > endDepth ) {
255223 for ( var j = 0; j < startDepth - endDepth && ca1; j++ ) {
256224 ca1 = ca1.parentNode.firstChild == ca1 ? ca1.parentNode : null;
@@ -318,8 +286,7 @@
319287 i++;
320288 }
321289 pos = newPos;
322 - node = next;
323 - depth = nextDepth;
 290+ t.goNext();
324291 }
325292
326293 // Remove markers that were previously inserted but weren't passed to this function
Index: trunk/extensions/UsabilityInitiative/js/plugins.combined.js
@@ -6972,7 +6972,7 @@
69736973 );
69746974 }
69756975 // Trigger the encapsulateSelection event (this might need to get named something else/done differently)
6976 - context.$content.trigger(
 6976+ $( context.$iframe[0].contentWindow.document ).trigger(
69776977 'encapsulateSelection', [ pre, options.peri, post, options.ownline, options.replace ]
69786978 );
69796979 return context.$textarea;
@@ -7067,7 +7067,7 @@
70687068 if ( typeof selector == 'undefined' ) {
70697069 selector = '*';
70707070 }
7071 - var e;
 7071+ var e, offset;
70727072 if ( context.$iframe[0].contentWindow.getSelection ) {
70737073 // Firefox and Opera
70747074 var selection = context.$iframe[0].contentWindow.getSelection();
@@ -7076,21 +7076,52 @@
70777077 // Start at the selection's start and traverse the DOM backwards
70787078 // This is done by traversing an element's children first, then the element itself, then its parent
70797079 e = selection.getRangeAt( 0 ).startContainer;
 7080+ offset = selection.startOffset;
70807081 } else {
70817082 return $( [] );
70827083 }
70837084 } else if ( context.$iframe[0].contentWindow.document.selection ) {
70847085 // IE
7085 - // TODO
7086 - return $( [] );
 7086+ // Because there's nothing like range.startContainer in IE, we need to do a DOM traversal
 7087+ // to find the element the start of the selection is in
 7088+ var range = context.$iframe[0].contentWindow.document.selection.createRange();
 7089+ // Set range2 to the text before the selection
 7090+ var range2 = context.$iframe[0].contentWindow.document.body.createTextRange();
 7091+ // For some reason this call throws errors in certain cases, e.g. when the selection is
 7092+ // not in the iframe
 7093+ try {
 7094+ range2.setEndPoint( 'EndToStart', range );
 7095+ } catch ( e ) {
 7096+ return $( [] );
 7097+ }
 7098+ var seekPos = range2.text.length;
 7099+
 7100+ var t = context.fn.traverser( context.$content );
 7101+ var pos = 0;
 7102+ while ( t.node ) {
 7103+ if ( t.node.nodeName != '#text' && t.node.nodeName != 'BR' ) {
 7104+ t.goNext();
 7105+ continue;
 7106+ }
 7107+ var newPos = t.node.nodeName == '#text' ? pos + t.node.nodeValue.length : pos + 1;
 7108+ if ( pos <= seekPos && seekPos < newPos ) {
 7109+ break;
 7110+ } else {
 7111+ pos = newPos;
 7112+ t.goNext();
 7113+ }
 7114+ }
 7115+ e = t.node;
 7116+ offset = seekPos - pos;
 7117+ if ( !e )
 7118+ return $( [] );
70877119 }
70887120 if ( e.nodeName != '#text' ) {
70897121 // The selection is not in a textnode, but between two non-text nodes
70907122 // (usually inside the <body> between two <br>s). Go to the rightmost
70917123 // child of the node just before the selection
70927124 var newE = e.firstChild;
7093 - for ( var i = 0; i < selection.startOffset - 1 && newE; i++ ) {
7094 - console.log( i );
 7125+ for ( var i = 0; i < offset - 1 && newE; i++ ) {
70957126 newE = newE.nextSibling;
70967127 }
70977128 while ( newE && newE.lastChild ) {
@@ -7109,6 +7140,60 @@
71107141 strict = false;
71117142 }
71127143 return $( [] );
 7144+ },
 7145+ /**
 7146+ * Get an object used to traverse the leaf nodes in the iframe DOM. This traversal skips leaf nodes
 7147+ * inside an element with the wikiEditor-noinclude class.
 7148+ *
 7149+ * Usage:
 7150+ * var t = context.fn.traverser( context.$content );
 7151+ * // t.node is the first textnode, t.depth is its depth
 7152+ * t.goNext();
 7153+ * // t.node is the second textnode, t.depth is its depth
 7154+ * // Trying to advance past the end will set t.node to null
 7155+ */
 7156+ 'traverser': function( start ) {
 7157+ function Traverser( start ) {
 7158+ this.goNext = function() {
 7159+ var p = this.node;
 7160+ nextDepth = this.depth;
 7161+ while ( p && !p.nextSibling ) {
 7162+ p = p.parentNode;
 7163+ nextDepth--;
 7164+ if ( this.depth == 0 ) {
 7165+ // We're back at the start node
 7166+ p = null;
 7167+ }
 7168+ }
 7169+ p = p ? p.nextSibling : null;
 7170+ do {
 7171+ // Filter nodes with the wikiEditor-noinclude class
 7172+ while ( p && $( p ).hasClass( 'wikiEditor-noinclude' ) ) {
 7173+ p = p.nextSibling;
 7174+ }
 7175+ if ( p && p.firstChild ) {
 7176+ p = p.firstChild;
 7177+ nextDepth++;
 7178+ }
 7179+ } while ( p && p.firstChild );
 7180+ this.node = p;
 7181+ this.depth = nextDepth;
 7182+ }
 7183+ // Find the leftmost leaf node in the tree
 7184+ this.node = start.jquery ? start.get( 0 ) : start;
 7185+ this.depth = 0;
 7186+ do {
 7187+ // Filter nodes with the wikiEditor-noinclude class
 7188+ while ( this.node && $( this.node ).hasClass( 'wikiEditor-noinclude' ) ) {
 7189+ this.node = this.node.nextSibling;
 7190+ }
 7191+ if ( this.node && this.node.firstChild ) {
 7192+ this.node = this.node.firstChild;
 7193+ this.depth++;
 7194+ }
 7195+ } while ( this.node && this.node.firstChild );
 7196+ }
 7197+ return new Traverser( start );
71137198 }
71147199
71157200 /*
@@ -7554,94 +7639,62 @@
75557640 context.fn.trigger( 'mark' );
75567641 markers.sort( function( a, b ) { return a.start - b.start || a.end - b.end; } );
75577642
7558 - // Traverse the iframe DOM, inserting markers where they're needed. The loop traverses all leaf nodes in the
7559 - // DOM, and uses DOM methods rather than jQuery because it has to work with text nodes and for performance.
 7643+ // Traverse the iframe DOM, inserting markers where they're needed.
75607644 var pos = 0;
7561 - var node = context.$content.get( 0 );
7562 - var next = null;
 7645+ var t = context.fn.traverser( context.$content );
75637646 var i = 0; // index for markers[]
75647647 var startNode = null;
7565 - var depth = 0, nextDepth = 0, startDepth = null;
 7648+ var nextDepth = 0, startDepth = null;
75667649 var lastTextNode = null, lastTextNodeDepth = null;
7567 - // Find the leftmost leaf node in the tree
7568 - while ( node.firstChild ) {
7569 - node = node.firstChild;
7570 - depth++;
7571 - // Filter nodes with the wikiEditor-noinclude class
7572 - while ( node && $( node ).hasClass( 'wikiEditor-noinclude' ) ) {
7573 - node = node.nextSibling;
7574 - }
7575 - }
7576 - while ( i < markers.length && node ) {
7577 - // Find the next leaf node
7578 - var p = node;
7579 - nextDepth = depth;
7580 - while ( p && !p.nextSibling ) {
7581 - p = p.parentNode;
7582 - nextDepth--;
7583 - }
7584 - // Filter nodes with the wikiEditor-noinclude class
7585 - p = p ? p.nextSibling : null;
7586 - do {
7587 - while ( p && $( p ).hasClass( 'wikiEditor-noinclude' ) ) {
7588 - p = p.nextSibling;
7589 - }
7590 - if ( p && p.firstChild ) {
7591 - p = p.firstChild;
7592 - nextDepth++;
7593 - }
7594 - } while ( p && ( $( p ).hasClass( 'wikiEditor-noinclude' ) || p.firstChild ) );
7595 -
7596 - next = p;
7597 - if ( node.nodeName != '#text' && node.nodeName != 'BR' ) {
 7650+ while ( i < markers.length && t.node ) {
 7651+ if ( t.node.nodeName != '#text' && t.node.nodeName != 'BR' ) {
75987652 // Skip this node
7599 - node = next;
7600 - depth = nextDepth;
 7653+ t.goNext();
76017654 continue;
76027655 }
7603 - var newPos = node.nodeName == '#text' ? pos + node.nodeValue.length : pos + 1;
7604 - if ( node.nodeName == '#text' ) {
7605 - lastTextNode = node;
7606 - lastTextNodeDepth = depth;
 7656+ var newPos = t.node.nodeName == '#text' ? pos + t.node.nodeValue.length : pos + 1;
 7657+ if ( t.node.nodeName == '#text' ) {
 7658+ lastTextNode = t.node;
 7659+ lastTextNodeDepth = t.depth;
76077660 }
76087661 // We want to isolate each marker, so we may need to split textNodes
76097662 // if a marker starts or end halfway one.
76107663 if ( !startNode && markers[i].start >= pos && markers[i].start < newPos ) {
76117664 // The next marker starts somewhere in this textNode or at this BR
76127665 if ( markers[i].start > pos ) {
7613 - // node must be a textnode at this point because
 7666+ // t.node must be a textnode at this point because
76147667 // start > pos and start < pos+1 can't both be true
76157668
76167669 // Split off the prefix
76177670 // This leaves the prefix in the current node and puts
76187671 // the rest in a new node, which we immediately advance to
7619 - node = node.splitText( markers[i].start - pos );
 7672+ t.node = t.node.splitText( markers[i].start - pos );
76207673 pos = markers[i].start;
76217674 }
7622 - startNode = node;
7623 - startDepth = depth;
 7675+ startNode = t.node;
 7676+ startDepth = t.depth;
76247677 }
76257678 // Don't wrap BRs, produces undesirable results
76267679 if ( startNode && startNode.nodeName == 'BR' ) {
7627 - startNode = node;
7628 - startDepth = depth;
 7680+ startNode = t.node;
 7681+ startDepth = t.depth;
76297682 }
76307683 // TODO: What happens when wrapping a zero-length string?
76317684 if ( startNode && markers[i].end > pos && markers[i].end <= newPos ) {
76327685 // The marker ends somewhere in this textNode or at this BR
76337686 if ( markers[i].end < newPos ) {
7634 - // node must be a textnode at this point because
 7687+ // t.node must be a textnode at this point because
76357688 // end > pos and end < pos+1 can't both be true
76367689
76377690 // Split off the suffix - This puts the suffix in a new node and leaves the rest in the current
7638 - // node. We have to make sure the split-off node will be visited correctly
7639 - // node.nodeValue.length - ( newPos - markers[i].end )
7640 - next = node.splitText( node.nodeValue.length - newPos + markers[i].end );
 7691+ // node.
 7692+ // t.node.nodeValue.length - ( newPos - markers[i].end )
 7693+ t.node.splitText( t.node.nodeValue.length - newPos + markers[i].end );
76417694 newPos = markers[i].end;
76427695 }
76437696
76447697 // Don't wrap leading or trailing BRs, doing that causes weird issues
7645 - var endNode = node, endDepth = depth;
 7698+ var endNode = t.node, endDepth = t.depth;
76467699 if ( endNode.nodeName == 'BR' ) {
76477700 endNode = lastTextNode;
76487701 endDepth = lastTextNodeDepth;
@@ -7653,7 +7706,7 @@
76547707 // rightmost leaves in the subtrees rooted at ca1 and ca2 respectively; if this is not the case, we
76557708 // can't cleanly wrap things without misnesting and we silently fail.
76567709 var ca1 = startNode, ca2 = endNode;
7657 - // Correct for startNode and node possibly not having the same depth
 7710+ // Correct for startNode and endNode possibly not having the same depth
76587711 if ( startDepth > endDepth ) {
76597712 for ( var j = 0; j < startDepth - endDepth && ca1; j++ ) {
76607713 ca1 = ca1.parentNode.firstChild == ca1 ? ca1.parentNode : null;
@@ -7722,8 +7775,7 @@
77237776 i++;
77247777 }
77257778 pos = newPos;
7726 - node = next;
7727 - depth = nextDepth;
 7779+ t.goNext();
77287780 }
77297781
77307782 // Remove markers that were previously inserted but weren't passed to this function
Index: trunk/extensions/UsabilityInitiative/js/plugins.combined.min.js
@@ -458,19 +458,28 @@
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.$content.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.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;}
463463 while(ec.firstChild&&ec.nodeName!='#text'){ec=ec.firstChild;}
464464 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;}
465465 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);}
466466 $element.trigger('scrollToTop');},'beforeSelection':function(selector,strict){if(typeof selector=='undefined'){selector='*';}
467 -var e;if(context.$iframe[0].contentWindow.getSelection){var selection=context.$iframe[0].contentWindow.getSelection();if(selection.baseNode!==null){e=selection.getRangeAt(0).startContainer;}else{return $([]);}}else if(context.$iframe[0].contentWindow.document.selection){return $([]);}
468 -if(e.nodeName!='#text'){var newE=e.firstChild;for(var i=0;i<selection.startOffset-1&&newE;i++){console.log(i);newE=newE.nextSibling;}
 467+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 $([]);}
 468+var seekPos=range2.text.length;var t=context.fn.traverser(context.$content);var pos=0;while(t.node){if(t.node.nodeName!='#text'&&t.node.nodeName!='BR'){t.goNext();continue;}
 469+var newPos=t.node.nodeName=='#text'?pos+t.node.nodeValue.length:pos+1;if(pos<=seekPos&&seekPos<newPos){break;}else{pos=newPos;t.goNext();}}
 470+e=t.node;offset=seekPos-pos;if(!e)
 471+return $([]);}
 472+if(e.nodeName!='#text'){var newE=e.firstChild;for(var i=0;i<offset-1&&newE;i++){newE=newE.nextSibling;}
469473 while(newE&&newE.lastChild){newE=newE.lastChild;}
470474 e=newE;}
471475 while(e){if($(e).is(selector)&&!strict)
472476 return $(e);var next=e.previousSibling;while(next&&next.lastChild){next=next.lastChild;}
473477 e=next||e.parentNode;strict=false;}
474 -return $([]);}};context.$textarea.wrap($('<div></div>').addClass('wikiEditor-ui')).wrap($('<div></div>').addClass('wikiEditor-ui-view wikiEditor-ui-view-wikitext')).wrap($('<div></div>').addClass('wikiEditor-ui-left')).wrap($('<div></div>').addClass('wikiEditor-ui-bottom')).wrap($('<div></div>').addClass('wikiEditor-ui-text'));context.$ui=context.$textarea.parent().parent().parent().parent().parent();context.$wikitext=context.$textarea.parent().parent().parent().parent();context.$wikitext.before($('<div></div>').addClass('wikiEditor-ui-controls').append($('<div></div>').addClass('wikiEditor-ui-tabs').hide()).append($('<div></div>').addClass('wikiEditor-ui-buttons'))).before($('<div style="clear:both;"></div>'));context.$controls=context.$ui.find('.wikiEditor-ui-buttons').hide();context.$buttons=context.$ui.find('.wikiEditor-ui-buttons');context.$tabs=context.$ui.find('.wikiEditor-ui-tabs');context.$ui.after($('<div style="clear:both;"></div>'));context.$wikitext.append($('<div></div>').addClass('wikiEditor-ui-right'));context.$wikitext.find('.wikiEditor-ui-left').prepend($('<div></div>').addClass('wikiEditor-ui-top'));context.view='wikitext';$(window).resize(function(event){context.fn.trigger('resize',event)});context.$iframe=$('<iframe></iframe>').attr({'frameBorder':0,'border':0,'src':wgScriptPath+'/extensions/UsabilityInitiative/js/plugins/jquery.wikiEditor.html?'+'instance='+context.instance+'&ts='+(new Date()).getTime(),'id':'wikiEditor-iframe-'+context.instance}).css({'backgroundColor':'white','width':'100%','height':context.$textarea.height(),'display':'none','overflow-y':'scroll','overflow-x':'hidden'}).insertAfter(context.$textarea).load(function(){if(!this.isSecondRun){context.$iframe[0].contentWindow.document.designMode='on';if($.browser.msie){this.isSecondRun=true;return;}}
 478+return $([]);},'traverser':function(start){function Traverser(start){this.goNext=function(){var p=this.node;nextDepth=this.depth;while(p&&!p.nextSibling){p=p.parentNode;nextDepth--;if(this.depth==0){p=null;}}
 479+p=p?p.nextSibling:null;do{while(p&&$(p).hasClass('wikiEditor-noinclude')){p=p.nextSibling;}
 480+if(p&&p.firstChild){p=p.firstChild;nextDepth++;}}while(p&&p.firstChild);this.node=p;this.depth=nextDepth;}
 481+this.node=start.jquery?start.get(0):start;this.depth=0;do{while(this.node&&$(this.node).hasClass('wikiEditor-noinclude')){this.node=this.node.nextSibling;}
 482+if(this.node&&this.node.firstChild){this.node=this.node.firstChild;this.depth++;}}while(this.node&&this.node.firstChild);}
 483+return new Traverser(start);}};context.$textarea.wrap($('<div></div>').addClass('wikiEditor-ui')).wrap($('<div></div>').addClass('wikiEditor-ui-view wikiEditor-ui-view-wikitext')).wrap($('<div></div>').addClass('wikiEditor-ui-left')).wrap($('<div></div>').addClass('wikiEditor-ui-bottom')).wrap($('<div></div>').addClass('wikiEditor-ui-text'));context.$ui=context.$textarea.parent().parent().parent().parent().parent();context.$wikitext=context.$textarea.parent().parent().parent().parent();context.$wikitext.before($('<div></div>').addClass('wikiEditor-ui-controls').append($('<div></div>').addClass('wikiEditor-ui-tabs').hide()).append($('<div></div>').addClass('wikiEditor-ui-buttons'))).before($('<div style="clear:both;"></div>'));context.$controls=context.$ui.find('.wikiEditor-ui-buttons').hide();context.$buttons=context.$ui.find('.wikiEditor-ui-buttons');context.$tabs=context.$ui.find('.wikiEditor-ui-tabs');context.$ui.after($('<div style="clear:both;"></div>'));context.$wikitext.append($('<div></div>').addClass('wikiEditor-ui-right'));context.$wikitext.find('.wikiEditor-ui-left').prepend($('<div></div>').addClass('wikiEditor-ui-top'));context.view='wikitext';$(window).resize(function(event){context.fn.trigger('resize',event)});context.$iframe=$('<iframe></iframe>').attr({'frameBorder':0,'border':0,'src':wgScriptPath+'/extensions/UsabilityInitiative/js/plugins/jquery.wikiEditor.html?'+'instance='+context.instance+'&ts='+(new Date()).getTime(),'id':'wikiEditor-iframe-'+context.instance}).css({'backgroundColor':'white','width':'100%','height':context.$textarea.height(),'display':'none','overflow-y':'scroll','overflow-x':'hidden'}).insertAfter(context.$textarea).load(function(){if(!this.isSecondRun){context.$iframe[0].contentWindow.document.designMode='on';if($.browser.msie){this.isSecondRun=true;return;}}
475484 context.$content=$(context.$iframe[0].contentWindow.document.body);var html=context.$textarea.val().replace(/\</g,'&lt;').replace(/\>/g,'&gt;');if($.browser.msie){if($.browser.versionNumber<=7){var prefix='',suffix=html;while(suffix){var match=suffix.match(/(^|\n) /);if(match){prefix+=suffix.substr(0,match.index+match[0].length-1)+'&nbsp;';suffix=suffix.substr(match.index+match[0].length);}else{break;}}
476485 html=prefix+suffix;}else{html=html.replace(/(^|\n) /g,"$1&nbsp;");}
477486 html=html.replace(/\t/g,'<span class="wikiEditor-tab"></span>');}
@@ -486,16 +495,13 @@
487496 var tokenArray=context.modules.highlight.tokenArray=[];var text=context.fn.getContents();for(module in $.wikiEditor.modules){if('exp'in $.wikiEditor.modules[module]){for(var i=0;i<$.wikiEditor.modules[module].exp.length;i++){var regex=$.wikiEditor.modules[module].exp[i].regex;var label=$.wikiEditor.modules[module].exp[i].label;var markAfter=false;if(typeof $.wikiEditor.modules[module].exp[i].markAfter!='undefined'){markAfter=true;}
488497 match=text.match(regex);var oldOffset=0;while(match!=null){var markOffset=0;var tokenStart=match.index+oldOffset+markOffset;if(markAfter){markOffset+=match[0].length;}
489498 tokenArray.push(new Token(match.index+oldOffset+markOffset,label,tokenStart,match));oldOffset+=match.index+match[0].length;newSubstring=text.substring(oldOffset);match=newSubstring.match(regex);}}}}
490 -tokenArray.sort(function(a,b){return a.offset-b.offset||a.tokenStart-b.tokenStart;});context.fn.trigger('scan');},mark:function(context,division,tokens){var markers=context.modules.highlight.markers=[];context.fn.trigger('mark');markers.sort(function(a,b){return a.start-b.start||a.end-b.end;});var pos=0;var node=context.$content.get(0);var next=null;var i=0;var startNode=null;var depth=0,nextDepth=0,startDepth=null;var lastTextNode=null,lastTextNodeDepth=null;while(node.firstChild){node=node.firstChild;depth++;while(node&&$(node).hasClass('wikiEditor-noinclude')){node=node.nextSibling;}}
491 -while(i<markers.length&&node){var p=node;nextDepth=depth;while(p&&!p.nextSibling){p=p.parentNode;nextDepth--;}
492 -p=p?p.nextSibling:null;do{while(p&&$(p).hasClass('wikiEditor-noinclude')){p=p.nextSibling;}
493 -if(p&&p.firstChild){p=p.firstChild;nextDepth++;}}while(p&&($(p).hasClass('wikiEditor-noinclude')||p.firstChild));next=p;if(node.nodeName!='#text'&&node.nodeName!='BR'){node=next;depth=nextDepth;continue;}
494 -var newPos=node.nodeName=='#text'?pos+node.nodeValue.length:pos+1;if(node.nodeName=='#text'){lastTextNode=node;lastTextNodeDepth=depth;}
495 -if(!startNode&&markers[i].start>=pos&&markers[i].start<newPos){if(markers[i].start>pos){node=node.splitText(markers[i].start-pos);pos=markers[i].start;}
496 -startNode=node;startDepth=depth;}
497 -if(startNode&&startNode.nodeName=='BR'){startNode=node;startDepth=depth;}
498 -if(startNode&&markers[i].end>pos&&markers[i].end<=newPos){if(markers[i].end<newPos){next=node.splitText(node.nodeValue.length-newPos+markers[i].end);newPos=markers[i].end;}
499 -var endNode=node,endDepth=depth;if(endNode.nodeName=='BR'){endNode=lastTextNode;endDepth=lastTextNodeDepth;}
 499+tokenArray.sort(function(a,b){return a.offset-b.offset||a.tokenStart-b.tokenStart;});context.fn.trigger('scan');},mark:function(context,division,tokens){var markers=context.modules.highlight.markers=[];context.fn.trigger('mark');markers.sort(function(a,b){return a.start-b.start||a.end-b.end;});var pos=0;var t=context.fn.traverser(context.$content);var i=0;var startNode=null;var nextDepth=0,startDepth=null;var lastTextNode=null,lastTextNodeDepth=null;while(i<markers.length&&t.node){if(t.node.nodeName!='#text'&&t.node.nodeName!='BR'){t.goNext();continue;}
 500+var newPos=t.node.nodeName=='#text'?pos+t.node.nodeValue.length:pos+1;if(t.node.nodeName=='#text'){lastTextNode=t.node;lastTextNodeDepth=t.depth;}
 501+if(!startNode&&markers[i].start>=pos&&markers[i].start<newPos){if(markers[i].start>pos){t.node=t.node.splitText(markers[i].start-pos);pos=markers[i].start;}
 502+startNode=t.node;startDepth=t.depth;}
 503+if(startNode&&startNode.nodeName=='BR'){startNode=t.node;startDepth=t.depth;}
 504+if(startNode&&markers[i].end>pos&&markers[i].end<=newPos){if(markers[i].end<newPos){t.node.splitText(t.node.nodeValue.length-newPos+markers[i].end);newPos=markers[i].end;}
 505+var endNode=t.node,endDepth=t.depth;if(endNode.nodeName=='BR'){endNode=lastTextNode;endDepth=lastTextNodeDepth;}
500506 var ca1=startNode,ca2=endNode;if(startDepth>endDepth){for(var j=0;j<startDepth-endDepth&&ca1;j++){ca1=ca1.parentNode.firstChild==ca1?ca1.parentNode:null;}}
501507 else if(startDepth<endDepth){for(var j=0;j<endDepth-startDepth&&ca2;j++){ca2=ca2.parentNode.lastChild==ca2?ca2.parentNode:null;}}
502508 while(ca1&&ca2&&ca1.parentNode!=ca2.parentNode){ca1=ca1.parentNode.firstChild==ca1?ca1.parentNode:null;ca2=ca2.parentNode.lastChild==ca2?ca2.parentNode:null;}
@@ -503,7 +509,7 @@
504510 if(nextNode){commonAncestor.insertBefore(newNode,nextNode);}else{commonAncestor.appendChild(newNode);}}else if(markers[i].anchor=='before'){commonAncestor.insertBefore(newNode,ca1);}else if(markers[i].anchor=='after'){if(nextNode){commonAncestor.insertBefore(newNode,nextNode);}else{commonAncestor.appendChild(newNode);}}
505511 $(newNode).data('marker',markers[i]).addClass('wikiEditor-highlight wikiEditor-highlight-tmp');markers[i].afterWrap(newNode,markers[i]);}else{$(anchor).addClass('wikiEditor-highlight-tmp').data('marker',markers[i]);markers[i].onSkip(anchor);}}
506512 startNode=null;startDepth=null;i++;}
507 -pos=newPos;node=next;depth=nextDepth;}
 513+pos=newPos;t.goNext();}
508514 context.$content.find('div.wikiEditor-highlight:not(.wikiEditor-highlight-tmp)').each(function(){if(typeof $(this).data('marker').unwrap=='function')
509515 $(this).data('marker').unwrap(this);if($(this).children().size()>0){$(this).replaceWith($(this).children());}else{$(this).replaceWith($(this).html());}});context.$content.find('div.wikiEditor-highlight-tmp').removeClass('wikiEditor-highlight-tmp');}}};})(jQuery);(function($){$.wikiEditor.modules.preview={fn:{create:function(context,config){if('initialized'in context.modules.preview){return;}
510516 context.modules.preview={'initialized':true,'previewText':null,'changesText':null};context.modules.preview.$preview=context.fn.addView({'name':'preview','titleMsg':'wikieditor-preview-tab','init':function(context){var wikitext=context.fn.getContents();if(context.modules.preview.previewText==wikitext){return;}

Status & tagging log