Index: trunk/extensions/UsabilityInitiative/UsabilityInitiative.hooks.php |
— | — | @@ -152,12 +152,12 @@ |
153 | 153 | array( |
154 | 154 | 'src' => 'js/plugins/jquery.wikiEditor.js', |
155 | 155 | 'class' => 'j.wikiEditor', |
156 | | - 'version' => 58 |
| 156 | + 'version' => 59 |
157 | 157 | ), |
158 | 158 | array( |
159 | 159 | 'src' => 'js/plugins/jquery.wikiEditor.highlight.js', |
160 | 160 | 'class' => 'j.wikiEditor.modules.highlight', |
161 | | - 'version' => 16 |
| 161 | + 'version' => 17 |
162 | 162 | ), |
163 | 163 | array( |
164 | 164 | 'src' => 'js/plugins/jquery.wikiEditor.toolbar.js', |
— | — | @@ -190,10 +190,10 @@ |
191 | 191 | 'version' => 1 ), |
192 | 192 | ), |
193 | 193 | 'combined' => array( |
194 | | - array( 'src' => 'js/plugins.combined.js', 'version' => 165 ), |
| 194 | + array( 'src' => 'js/plugins.combined.js', 'version' => 166 ), |
195 | 195 | ), |
196 | 196 | 'minified' => array( |
197 | | - array( 'src' => 'js/plugins.combined.min.js', 'version' => 165 ), |
| 197 | + array( 'src' => 'js/plugins.combined.min.js', 'version' => 166 ), |
198 | 198 | ), |
199 | 199 | ), |
200 | 200 | ); |
Index: trunk/extensions/UsabilityInitiative/js/plugins/jquery.wikiEditor.js |
— | — | @@ -491,7 +491,7 @@ |
492 | 492 | ); |
493 | 493 | } |
494 | 494 | // 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( |
496 | 496 | 'encapsulateSelection', [ pre, options.peri, post, options.ownline, options.replace ] |
497 | 497 | ); |
498 | 498 | return context.$textarea; |
— | — | @@ -586,7 +586,7 @@ |
587 | 587 | if ( typeof selector == 'undefined' ) { |
588 | 588 | selector = '*'; |
589 | 589 | } |
590 | | - var e; |
| 590 | + var e, offset; |
591 | 591 | if ( context.$iframe[0].contentWindow.getSelection ) { |
592 | 592 | // Firefox and Opera |
593 | 593 | var selection = context.$iframe[0].contentWindow.getSelection(); |
— | — | @@ -595,21 +595,52 @@ |
596 | 596 | // Start at the selection's start and traverse the DOM backwards |
597 | 597 | // This is done by traversing an element's children first, then the element itself, then its parent |
598 | 598 | e = selection.getRangeAt( 0 ).startContainer; |
| 599 | + offset = selection.startOffset; |
599 | 600 | } else { |
600 | 601 | return $( [] ); |
601 | 602 | } |
602 | 603 | } else if ( context.$iframe[0].contentWindow.document.selection ) { |
603 | 604 | // 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 $( [] ); |
606 | 638 | } |
607 | 639 | if ( e.nodeName != '#text' ) { |
608 | 640 | // The selection is not in a textnode, but between two non-text nodes |
609 | 641 | // (usually inside the <body> between two <br>s). Go to the rightmost |
610 | 642 | // child of the node just before the selection |
611 | 643 | 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++ ) { |
614 | 645 | newE = newE.nextSibling; |
615 | 646 | } |
616 | 647 | while ( newE && newE.lastChild ) { |
— | — | @@ -628,6 +659,60 @@ |
629 | 660 | strict = false; |
630 | 661 | } |
631 | 662 | 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 ); |
632 | 717 | } |
633 | 718 | |
634 | 719 | /* |
Index: trunk/extensions/UsabilityInitiative/js/plugins/jquery.wikiEditor.highlight.js |
— | — | @@ -150,94 +150,62 @@ |
151 | 151 | context.fn.trigger( 'mark' ); |
152 | 152 | markers.sort( function( a, b ) { return a.start - b.start || a.end - b.end; } ); |
153 | 153 | |
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. |
156 | 155 | var pos = 0; |
157 | | - var node = context.$content.get( 0 ); |
158 | | - var next = null; |
| 156 | + var t = context.fn.traverser( context.$content ); |
159 | 157 | var i = 0; // index for markers[] |
160 | 158 | var startNode = null; |
161 | | - var depth = 0, nextDepth = 0, startDepth = null; |
| 159 | + var nextDepth = 0, startDepth = null; |
162 | 160 | 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' ) { |
194 | 163 | // Skip this node |
195 | | - node = next; |
196 | | - depth = nextDepth; |
| 164 | + t.goNext(); |
197 | 165 | continue; |
198 | 166 | } |
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; |
203 | 171 | } |
204 | 172 | // We want to isolate each marker, so we may need to split textNodes |
205 | 173 | // if a marker starts or end halfway one. |
206 | 174 | if ( !startNode && markers[i].start >= pos && markers[i].start < newPos ) { |
207 | 175 | // The next marker starts somewhere in this textNode or at this BR |
208 | 176 | 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 |
210 | 178 | // start > pos and start < pos+1 can't both be true |
211 | 179 | |
212 | 180 | // Split off the prefix |
213 | 181 | // This leaves the prefix in the current node and puts |
214 | 182 | // 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 ); |
216 | 184 | pos = markers[i].start; |
217 | 185 | } |
218 | | - startNode = node; |
219 | | - startDepth = depth; |
| 186 | + startNode = t.node; |
| 187 | + startDepth = t.depth; |
220 | 188 | } |
221 | 189 | // Don't wrap BRs, produces undesirable results |
222 | 190 | if ( startNode && startNode.nodeName == 'BR' ) { |
223 | | - startNode = node; |
224 | | - startDepth = depth; |
| 191 | + startNode = t.node; |
| 192 | + startDepth = t.depth; |
225 | 193 | } |
226 | 194 | // TODO: What happens when wrapping a zero-length string? |
227 | 195 | if ( startNode && markers[i].end > pos && markers[i].end <= newPos ) { |
228 | 196 | // The marker ends somewhere in this textNode or at this BR |
229 | 197 | 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 |
231 | 199 | // end > pos and end < pos+1 can't both be true |
232 | 200 | |
233 | 201 | // 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 ); |
237 | 205 | newPos = markers[i].end; |
238 | 206 | } |
239 | 207 | |
240 | 208 | // 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; |
242 | 210 | if ( endNode.nodeName == 'BR' ) { |
243 | 211 | endNode = lastTextNode; |
244 | 212 | endDepth = lastTextNodeDepth; |
— | — | @@ -249,7 +217,7 @@ |
250 | 218 | // rightmost leaves in the subtrees rooted at ca1 and ca2 respectively; if this is not the case, we |
251 | 219 | // can't cleanly wrap things without misnesting and we silently fail. |
252 | 220 | 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 |
254 | 222 | if ( startDepth > endDepth ) { |
255 | 223 | for ( var j = 0; j < startDepth - endDepth && ca1; j++ ) { |
256 | 224 | ca1 = ca1.parentNode.firstChild == ca1 ? ca1.parentNode : null; |
— | — | @@ -318,8 +286,7 @@ |
319 | 287 | i++; |
320 | 288 | } |
321 | 289 | pos = newPos; |
322 | | - node = next; |
323 | | - depth = nextDepth; |
| 290 | + t.goNext(); |
324 | 291 | } |
325 | 292 | |
326 | 293 | // 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 @@ |
6973 | 6973 | ); |
6974 | 6974 | } |
6975 | 6975 | // 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( |
6977 | 6977 | 'encapsulateSelection', [ pre, options.peri, post, options.ownline, options.replace ] |
6978 | 6978 | ); |
6979 | 6979 | return context.$textarea; |
— | — | @@ -7067,7 +7067,7 @@ |
7068 | 7068 | if ( typeof selector == 'undefined' ) { |
7069 | 7069 | selector = '*'; |
7070 | 7070 | } |
7071 | | - var e; |
| 7071 | + var e, offset; |
7072 | 7072 | if ( context.$iframe[0].contentWindow.getSelection ) { |
7073 | 7073 | // Firefox and Opera |
7074 | 7074 | var selection = context.$iframe[0].contentWindow.getSelection(); |
— | — | @@ -7076,21 +7076,52 @@ |
7077 | 7077 | // Start at the selection's start and traverse the DOM backwards |
7078 | 7078 | // This is done by traversing an element's children first, then the element itself, then its parent |
7079 | 7079 | e = selection.getRangeAt( 0 ).startContainer; |
| 7080 | + offset = selection.startOffset; |
7080 | 7081 | } else { |
7081 | 7082 | return $( [] ); |
7082 | 7083 | } |
7083 | 7084 | } else if ( context.$iframe[0].contentWindow.document.selection ) { |
7084 | 7085 | // 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 $( [] ); |
7087 | 7119 | } |
7088 | 7120 | if ( e.nodeName != '#text' ) { |
7089 | 7121 | // The selection is not in a textnode, but between two non-text nodes |
7090 | 7122 | // (usually inside the <body> between two <br>s). Go to the rightmost |
7091 | 7123 | // child of the node just before the selection |
7092 | 7124 | 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++ ) { |
7095 | 7126 | newE = newE.nextSibling; |
7096 | 7127 | } |
7097 | 7128 | while ( newE && newE.lastChild ) { |
— | — | @@ -7109,6 +7140,60 @@ |
7110 | 7141 | strict = false; |
7111 | 7142 | } |
7112 | 7143 | 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 ); |
7113 | 7198 | } |
7114 | 7199 | |
7115 | 7200 | /* |
— | — | @@ -7554,94 +7639,62 @@ |
7555 | 7640 | context.fn.trigger( 'mark' ); |
7556 | 7641 | markers.sort( function( a, b ) { return a.start - b.start || a.end - b.end; } ); |
7557 | 7642 | |
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. |
7560 | 7644 | var pos = 0; |
7561 | | - var node = context.$content.get( 0 ); |
7562 | | - var next = null; |
| 7645 | + var t = context.fn.traverser( context.$content ); |
7563 | 7646 | var i = 0; // index for markers[] |
7564 | 7647 | var startNode = null; |
7565 | | - var depth = 0, nextDepth = 0, startDepth = null; |
| 7648 | + var nextDepth = 0, startDepth = null; |
7566 | 7649 | 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' ) { |
7598 | 7652 | // Skip this node |
7599 | | - node = next; |
7600 | | - depth = nextDepth; |
| 7653 | + t.goNext(); |
7601 | 7654 | continue; |
7602 | 7655 | } |
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; |
7607 | 7660 | } |
7608 | 7661 | // We want to isolate each marker, so we may need to split textNodes |
7609 | 7662 | // if a marker starts or end halfway one. |
7610 | 7663 | if ( !startNode && markers[i].start >= pos && markers[i].start < newPos ) { |
7611 | 7664 | // The next marker starts somewhere in this textNode or at this BR |
7612 | 7665 | 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 |
7614 | 7667 | // start > pos and start < pos+1 can't both be true |
7615 | 7668 | |
7616 | 7669 | // Split off the prefix |
7617 | 7670 | // This leaves the prefix in the current node and puts |
7618 | 7671 | // 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 ); |
7620 | 7673 | pos = markers[i].start; |
7621 | 7674 | } |
7622 | | - startNode = node; |
7623 | | - startDepth = depth; |
| 7675 | + startNode = t.node; |
| 7676 | + startDepth = t.depth; |
7624 | 7677 | } |
7625 | 7678 | // Don't wrap BRs, produces undesirable results |
7626 | 7679 | if ( startNode && startNode.nodeName == 'BR' ) { |
7627 | | - startNode = node; |
7628 | | - startDepth = depth; |
| 7680 | + startNode = t.node; |
| 7681 | + startDepth = t.depth; |
7629 | 7682 | } |
7630 | 7683 | // TODO: What happens when wrapping a zero-length string? |
7631 | 7684 | if ( startNode && markers[i].end > pos && markers[i].end <= newPos ) { |
7632 | 7685 | // The marker ends somewhere in this textNode or at this BR |
7633 | 7686 | 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 |
7635 | 7688 | // end > pos and end < pos+1 can't both be true |
7636 | 7689 | |
7637 | 7690 | // 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 ); |
7641 | 7694 | newPos = markers[i].end; |
7642 | 7695 | } |
7643 | 7696 | |
7644 | 7697 | // 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; |
7646 | 7699 | if ( endNode.nodeName == 'BR' ) { |
7647 | 7700 | endNode = lastTextNode; |
7648 | 7701 | endDepth = lastTextNodeDepth; |
— | — | @@ -7653,7 +7706,7 @@ |
7654 | 7707 | // rightmost leaves in the subtrees rooted at ca1 and ca2 respectively; if this is not the case, we |
7655 | 7708 | // can't cleanly wrap things without misnesting and we silently fail. |
7656 | 7709 | 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 |
7658 | 7711 | if ( startDepth > endDepth ) { |
7659 | 7712 | for ( var j = 0; j < startDepth - endDepth && ca1; j++ ) { |
7660 | 7713 | ca1 = ca1.parentNode.firstChild == ca1 ? ca1.parentNode : null; |
— | — | @@ -7722,8 +7775,7 @@ |
7723 | 7776 | i++; |
7724 | 7777 | } |
7725 | 7778 | pos = newPos; |
7726 | | - node = next; |
7727 | | - depth = nextDepth; |
| 7779 | + t.goNext(); |
7728 | 7780 | } |
7729 | 7781 | |
7730 | 7782 | // 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 @@ |
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.$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;} |
463 | 463 | while(ec.firstChild&&ec.nodeName!='#text'){ec=ec.firstChild;} |
464 | 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;} |
465 | 465 | 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 | 466 | $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;} |
469 | 473 | while(newE&&newE.lastChild){newE=newE.lastChild;} |
470 | 474 | e=newE;} |
471 | 475 | while(e){if($(e).is(selector)&&!strict) |
472 | 476 | return $(e);var next=e.previousSibling;while(next&&next.lastChild){next=next.lastChild;} |
473 | 477 | 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;}} |
475 | 484 | context.$content=$(context.$iframe[0].contentWindow.document.body);var html=context.$textarea.val().replace(/\</g,'<').replace(/\>/g,'>');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)+' ';suffix=suffix.substr(match.index+match[0].length);}else{break;}} |
476 | 485 | html=prefix+suffix;}else{html=html.replace(/(^|\n) /g,"$1 ");} |
477 | 486 | html=html.replace(/\t/g,'<span class="wikiEditor-tab"></span>');} |
— | — | @@ -486,16 +495,13 @@ |
487 | 496 | 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;} |
488 | 497 | 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;} |
489 | 498 | 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;} |
500 | 506 | 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;}} |
501 | 507 | else if(startDepth<endDepth){for(var j=0;j<endDepth-startDepth&&ca2;j++){ca2=ca2.parentNode.lastChild==ca2?ca2.parentNode:null;}} |
502 | 508 | 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 @@ |
504 | 510 | 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);}} |
505 | 511 | $(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);}} |
506 | 512 | startNode=null;startDepth=null;i++;} |
507 | | -pos=newPos;node=next;depth=nextDepth;} |
| 513 | +pos=newPos;t.goNext();} |
508 | 514 | context.$content.find('div.wikiEditor-highlight:not(.wikiEditor-highlight-tmp)').each(function(){if(typeof $(this).data('marker').unwrap=='function') |
509 | 515 | $(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;} |
510 | 516 | 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;} |