Index: trunk/extensions/UsabilityInitiative/UsabilityInitiative.hooks.php |
— | — | @@ -72,8 +72,8 @@ |
73 | 73 | array( 'src' => 'js/plugins/jquery.namespaceSelect.js', 'version' => 1 ), |
74 | 74 | array( 'src' => 'js/plugins/jquery.suggestions.js', 'version' => 7 ), |
75 | 75 | array( 'src' => 'js/plugins/jquery.textSelection.js', 'version' => 26 ), |
76 | | - array( 'src' => 'js/plugins/jquery.wikiEditor.js', 'version' => 82 ), |
77 | | - array( 'src' => 'js/plugins/jquery.wikiEditor.highlight.js', 'version' => 25 ), |
| 76 | + array( 'src' => 'js/plugins/jquery.wikiEditor.js', 'version' => 83 ), |
| 77 | + array( 'src' => 'js/plugins/jquery.wikiEditor.highlight.js', 'version' => 26 ), |
78 | 78 | array( 'src' => 'js/plugins/jquery.wikiEditor.toolbar.js', 'version' => 45 ), |
79 | 79 | array( 'src' => 'js/plugins/jquery.wikiEditor.dialogs.js', 'version' => 11 ), |
80 | 80 | array( 'src' => 'js/plugins/jquery.wikiEditor.toc.js', 'version' => 75 ), |
— | — | @@ -82,10 +82,10 @@ |
83 | 83 | array( 'src' => 'js/plugins/jquery.wikiEditor.publish.js', 'version' => 2 ), |
84 | 84 | ), |
85 | 85 | 'combined' => array( |
86 | | - array( 'src' => 'js/plugins.combined.js', 'version' => 195 ), |
| 86 | + array( 'src' => 'js/plugins.combined.js', 'version' => 196 ), |
87 | 87 | ), |
88 | 88 | 'minified' => array( |
89 | | - array( 'src' => 'js/plugins.combined.min.js', 'version' => 195 ), |
| 89 | + array( 'src' => 'js/plugins.combined.min.js', 'version' => 196 ), |
90 | 90 | ), |
91 | 91 | ), |
92 | 92 | ); |
Index: trunk/extensions/UsabilityInitiative/js/plugins/jquery.wikiEditor.js |
— | — | @@ -376,7 +376,19 @@ |
377 | 377 | // .find( '* + p' ) isn't good enough because textnodes aren't considered |
378 | 378 | $pre.find( 'p' ).each( function() { |
379 | 379 | if ( this.previousSibling || this.parentNode != $pre.get( 0 ) ) { |
380 | | - $( this ).text( "\n" + $( this ).text() ); |
| 380 | + var text = "\n" + $( this ).text(); |
| 381 | + // If this <p> is preceded by some text, add a \n at the beginning, and if |
| 382 | + // it's followed by a textnode, add a \n at the end |
| 383 | + // We need the traverser because there can be other weird stuff in between |
| 384 | + // TODO: We need a reverse traverser, write this |
| 385 | + var t = new context.fn.rawTraverser( this.lastChild, -10, this ).next(); |
| 386 | + while ( t && t.node.nodeName != '#text' && t.node.nodeName != 'BR' && t.node.nodeName != 'P' ) { |
| 387 | + t = t.next(); |
| 388 | + } |
| 389 | + if ( t && !t.inP && t.node.nodeName != 'P' ) { |
| 390 | + text += "\n"; |
| 391 | + } |
| 392 | + $( this ).text( text ); |
381 | 393 | } |
382 | 394 | } ); |
383 | 395 | return $pre.text(); |
— | — | @@ -742,8 +754,52 @@ |
743 | 755 | return retval || $( [] ); |
744 | 756 | }, |
745 | 757 | /** |
| 758 | + * Object used by traverser(). Don't use this unless you know what you're doing |
| 759 | + */ |
| 760 | + 'rawTraverser': function( node, depth, inP ) { |
| 761 | + this.node = node; |
| 762 | + this.depth = depth; |
| 763 | + this.inP = inP; |
| 764 | + this.next = function() { |
| 765 | + var p = this.node; |
| 766 | + var nextDepth = this.depth; |
| 767 | + var nextInP = this.inP; |
| 768 | + while ( p && !p.nextSibling ) { |
| 769 | + p = p.parentNode; |
| 770 | + nextDepth--; |
| 771 | + if ( nextDepth == 0 ) { |
| 772 | + // We're back at the start node |
| 773 | + p = null; |
| 774 | + } |
| 775 | + if ( p && p.nodeName == "P" ) { |
| 776 | + nextInP = null; |
| 777 | + } |
| 778 | + } |
| 779 | + p = p ? p.nextSibling : null; |
| 780 | + if ( p && p.nodeName == "P" ) { |
| 781 | + nextInP = p; |
| 782 | + } |
| 783 | + do { |
| 784 | + // Filter nodes with the wikiEditor-noinclude class |
| 785 | + // Don't use $( p ).hasClass( 'wikiEditor-noinclude' ) because |
| 786 | + // $() is slow in a tight loop |
| 787 | + while ( p && ( ' ' + p.className + ' ' ).indexOf( ' wikiEditor-noinclude ' ) != -1 ) { |
| 788 | + p = p.nextSibling; |
| 789 | + } |
| 790 | + if ( p && p.firstChild ) { |
| 791 | + p = p.firstChild; |
| 792 | + nextDepth++; |
| 793 | + if ( p.nodeName == "P" ) { |
| 794 | + nextInP = p; |
| 795 | + } |
| 796 | + } |
| 797 | + } while ( p && p.firstChild ); |
| 798 | + return p ? new context.fn.rawTraverser( p, nextDepth, nextInP ) : null; |
| 799 | + }; |
| 800 | + }, |
| 801 | + /** |
746 | 802 | * Get an object used to traverse the leaf nodes in the iframe DOM. This traversal skips leaf nodes |
747 | | - * inside an element with the wikiEditor-noinclude class. |
| 803 | + * inside an element with the wikiEditor-noinclude class. This basically wraps rawTraverser |
748 | 804 | * |
749 | 805 | * Usage: |
750 | 806 | * var t = context.fn.traverser( context.$content ); |
— | — | @@ -753,48 +809,10 @@ |
754 | 810 | * // Trying to advance past the end will set t.node to null |
755 | 811 | */ |
756 | 812 | 'traverser': function( start ) { |
757 | | - function Traverser( node, depth, inP ) { |
758 | | - this.node = node; |
759 | | - this.depth = depth; |
760 | | - this.inP = inP; |
761 | | - this.next = function() { |
762 | | - var p = this.node; |
763 | | - var nextDepth = this.depth; |
764 | | - var nextInP = this.inP; |
765 | | - while ( p && !p.nextSibling ) { |
766 | | - if ( p.nodeName == "P" ) { |
767 | | - nextInP = false; |
768 | | - } |
769 | | - p = p.parentNode; |
770 | | - nextDepth--; |
771 | | - if ( nextDepth == 0 ) { |
772 | | - // We're back at the start node |
773 | | - p = null; |
774 | | - } |
775 | | - } |
776 | | - p = p ? p.nextSibling : null; |
777 | | - do { |
778 | | - // Filter nodes with the wikiEditor-noinclude class |
779 | | - // Don't use $( p ).hasClass( 'wikiEditor-noinclude' ) because |
780 | | - // $() is slow in a tight loop |
781 | | - while ( p && ( ' ' + p.className + ' ' ).indexOf( ' wikiEditor-noinclude ' ) != -1 ) { |
782 | | - p = p.nextSibling; |
783 | | - } |
784 | | - if ( p && p.firstChild ) { |
785 | | - p = p.firstChild; |
786 | | - nextDepth++; |
787 | | - if ( p.nodeName == "P" ) { |
788 | | - nextInP = true; |
789 | | - } |
790 | | - } |
791 | | - } while ( p && p.firstChild ); |
792 | | - return p ? new Traverser( p, nextDepth, nextInP ) : null; |
793 | | - }; |
794 | | - } |
795 | 813 | // Find the leftmost leaf node in the tree |
796 | 814 | var node = start.jquery ? start.get( 0 ) : start; |
797 | 815 | var depth = 0; |
798 | | - var inP = node.nodeName == "P"; |
| 816 | + var inP = node.nodeName == "P" ? node : null; |
799 | 817 | do { |
800 | 818 | // Filter nodes with the wikiEditor-noinclude class |
801 | 819 | // Don't use $( p ).hasClass( 'wikiEditor-noinclude' ) because |
— | — | @@ -806,11 +824,11 @@ |
807 | 825 | node = node.firstChild; |
808 | 826 | depth++; |
809 | 827 | if ( node.nodeName == "P" ) { |
810 | | - inP = true; |
| 828 | + inP = node; |
811 | 829 | } |
812 | 830 | } |
813 | 831 | } while ( node && node.firstChild ); |
814 | | - return new Traverser( node, depth, inP ); |
| 832 | + return new context.fn.rawTraverser( node, depth, inP ); |
815 | 833 | }, |
816 | 834 | 'getOffset': function( offset ) { |
817 | 835 | if ( !context.offsets ) { |
— | — | @@ -849,13 +867,13 @@ |
850 | 868 | var t = context.fn.traverser( context.$content ); |
851 | 869 | var pos = 0, lastTextNode = null, lastTextNodeDepth = null; |
852 | 870 | while ( t ) { |
853 | | - if ( t.node.nodeName != '#text' && t.node.nodeName != 'BR' ) { |
| 871 | + if ( t.node.nodeName != '#text' && t.node.nodeName != 'BR' && t.node.nodeName != 'P' ) { |
854 | 872 | t = t.next(); |
855 | 873 | continue; |
856 | 874 | } |
857 | 875 | var nextPos = t.node.nodeName == '#text' ? pos + t.node.nodeValue.length : pos + 1; |
858 | 876 | var nextT = t.next(); |
859 | | - var leavingP = t.inP && nextT && !nextT.inP; |
| 877 | + var leavingP = t.node.nodeName != 'P' && t.inP && nextT && ( !nextT.inP || nextT.inP != t.inP ); |
860 | 878 | context.offsets[pos] = { |
861 | 879 | 'node': t.node, |
862 | 880 | 'offset': 0, |
Index: trunk/extensions/UsabilityInitiative/js/plugins/jquery.wikiEditor.highlight.js |
— | — | @@ -163,23 +163,27 @@ |
164 | 164 | } |
165 | 165 | var startNode = s.node; |
166 | 166 | var startDepth = s.depth; |
167 | | - // The next marker starts somewhere in this textNode or at this BR |
168 | | - if ( s.offset > 0 ) { |
169 | | - // t.node must be a textnode at this point because |
170 | | - // only textnodes can have offset > 0 |
171 | | - |
172 | | - // Split off the prefix |
173 | | - // This leaves the prefix in the current node and puts |
174 | | - // the rest in a new node which is our start node |
175 | | - startNode = startNode.splitText( s.offset ); |
176 | | - } |
| 167 | + |
177 | 168 | // Don't wrap leading BRs, produces undesirable results |
178 | | - while ( startNode.nodeName == 'BR' ) { |
| 169 | + // FIXME: It's also possible that the offset is a bit high because getOffset() has incremented |
| 170 | + // .length to fake the newline caused by startNode being in a P. In this case, prevent |
| 171 | + // the textnode splitting below from making startNode an empty textnode, IE barfs on that |
| 172 | + while ( startNode.nodeName == 'BR' || s.offset == startNode.nodeValue.length ) { |
179 | 173 | start++; |
180 | 174 | s = context.fn.getOffset( start ); |
181 | 175 | startNode = s.node; |
182 | 176 | startDepth = s.depth; |
183 | 177 | } |
| 178 | + |
| 179 | + // The next marker starts somewhere in this textNode or at this BR |
| 180 | + if ( s.offset > 0 && s.node.nodeName == '#text' ) { |
| 181 | + // Split off the prefix |
| 182 | + // This leaves the prefix in the current node and puts |
| 183 | + // the rest in a new node which is our start node |
| 184 | + startNode = startNode.splitText( s.offset ); |
| 185 | + // This also invalidates cached offset objects |
| 186 | + context.fn.purgeOffsets(); // TODO: Optimize better, get end offset object earlier |
| 187 | + } |
184 | 188 | |
185 | 189 | var end = markers[i].end; |
186 | 190 | var e = context.fn.getOffset( end ); |
— | — | @@ -189,14 +193,13 @@ |
190 | 194 | } |
191 | 195 | var endNode = e.node; |
192 | 196 | var endDepth = e.depth; |
193 | | - if ( e.offset < e.length - 1 ) { |
194 | | - // t.node must be a textnode at this point because |
195 | | - // .length is 1 for BRs and offset can't be < 0 |
196 | | - |
| 197 | + if ( e.offset < e.length - 1 && e.node.nodeName == '#text' ) { |
197 | 198 | // Split off the suffix - This puts the suffix in a new node and leaves the rest in the current |
198 | 199 | // node. |
199 | 200 | // endNode.nodeValue.length - ( newPos - markers[i].end ) |
200 | 201 | endNode.splitText( e.offset + 1 ); |
| 202 | + // This also invalidates cached offset objects |
| 203 | + context.fn.purgeOffsets(); // TODO: Optimize better, get end offset object earlier |
201 | 204 | } |
202 | 205 | |
203 | 206 | // Don't wrap trailing BRs, doing that causes weird issues |
— | — | @@ -236,13 +239,19 @@ |
237 | 240 | ca1 = ca1.parentNode.firstChild == ca1 ? ca1.parentNode : null; |
238 | 241 | ca2 = ca2.parentNode.lastChild == ca2 ? ca2.parentNode : null; |
239 | 242 | } |
240 | | - if ( ca1 && ca2 && ca1.parentNode && ca2.nextSibling ) { |
| 243 | + if ( ca1 && ca2 && ca1.parentNode ) { |
241 | 244 | var anchor = markers[i].getAnchor( ca1, ca2 ); |
242 | 245 | if ( !anchor ) { |
243 | 246 | // We have to store things like .parentNode and .nextSibling because appendChild() changes these |
244 | 247 | // properties |
245 | 248 | var newNode = ca1.ownerDocument.createElement( 'div' ); |
246 | 249 | var commonAncestor = ca1.parentNode; |
| 250 | + // Special case: can't put block elements in a <p> |
| 251 | + if ( commonAncestor.nodeName == 'P' && commonAncestor.parentNode ) { |
| 252 | + commonAncestor = commonAncestor.parentNode; |
| 253 | + ca1 = ca1.parentNode; |
| 254 | + ca2 = ca2.parentNode; |
| 255 | + } |
247 | 256 | var nextNode = ca2.nextSibling; |
248 | 257 | if ( markers[i].anchor == 'wrap' ) { |
249 | 258 | // Append all nodes between ca1 and ca2 (inclusive) to newNode |
Index: trunk/extensions/UsabilityInitiative/js/plugins.combined.js |
— | — | @@ -6800,7 +6800,19 @@ |
6801 | 6801 | // .find( '* + p' ) isn't good enough because textnodes aren't considered |
6802 | 6802 | $pre.find( 'p' ).each( function() { |
6803 | 6803 | if ( this.previousSibling || this.parentNode != $pre.get( 0 ) ) { |
6804 | | - $( this ).text( "\n" + $( this ).text() ); |
| 6804 | + var text = "\n" + $( this ).text(); |
| 6805 | + // If this <p> is preceded by some text, add a \n at the beginning, and if |
| 6806 | + // it's followed by a textnode, add a \n at the end |
| 6807 | + // We need the traverser because there can be other weird stuff in between |
| 6808 | + // TODO: We need a reverse traverser, write this |
| 6809 | + var t = new context.fn.rawTraverser( this.lastChild, -10, this ).next(); |
| 6810 | + while ( t && t.node.nodeName != '#text' && t.node.nodeName != 'BR' && t.node.nodeName != 'P' ) { |
| 6811 | + t = t.next(); |
| 6812 | + } |
| 6813 | + if ( t && !t.inP && t.node.nodeName != 'P' ) { |
| 6814 | + text += "\n"; |
| 6815 | + } |
| 6816 | + $( this ).text( text ); |
6805 | 6817 | } |
6806 | 6818 | } ); |
6807 | 6819 | return $pre.text(); |
— | — | @@ -7166,8 +7178,52 @@ |
7167 | 7179 | return retval || $( [] ); |
7168 | 7180 | }, |
7169 | 7181 | /** |
| 7182 | + * Object used by traverser(). Don't use this unless you know what you're doing |
| 7183 | + */ |
| 7184 | + 'rawTraverser': function( node, depth, inP ) { |
| 7185 | + this.node = node; |
| 7186 | + this.depth = depth; |
| 7187 | + this.inP = inP; |
| 7188 | + this.next = function() { |
| 7189 | + var p = this.node; |
| 7190 | + var nextDepth = this.depth; |
| 7191 | + var nextInP = this.inP; |
| 7192 | + while ( p && !p.nextSibling ) { |
| 7193 | + p = p.parentNode; |
| 7194 | + nextDepth--; |
| 7195 | + if ( nextDepth == 0 ) { |
| 7196 | + // We're back at the start node |
| 7197 | + p = null; |
| 7198 | + } |
| 7199 | + if ( p && p.nodeName == "P" ) { |
| 7200 | + nextInP = null; |
| 7201 | + } |
| 7202 | + } |
| 7203 | + p = p ? p.nextSibling : null; |
| 7204 | + if ( p && p.nodeName == "P" ) { |
| 7205 | + nextInP = p; |
| 7206 | + } |
| 7207 | + do { |
| 7208 | + // Filter nodes with the wikiEditor-noinclude class |
| 7209 | + // Don't use $( p ).hasClass( 'wikiEditor-noinclude' ) because |
| 7210 | + // $() is slow in a tight loop |
| 7211 | + while ( p && ( ' ' + p.className + ' ' ).indexOf( ' wikiEditor-noinclude ' ) != -1 ) { |
| 7212 | + p = p.nextSibling; |
| 7213 | + } |
| 7214 | + if ( p && p.firstChild ) { |
| 7215 | + p = p.firstChild; |
| 7216 | + nextDepth++; |
| 7217 | + if ( p.nodeName == "P" ) { |
| 7218 | + nextInP = p; |
| 7219 | + } |
| 7220 | + } |
| 7221 | + } while ( p && p.firstChild ); |
| 7222 | + return p ? new context.fn.rawTraverser( p, nextDepth, nextInP ) : null; |
| 7223 | + }; |
| 7224 | + }, |
| 7225 | + /** |
7170 | 7226 | * Get an object used to traverse the leaf nodes in the iframe DOM. This traversal skips leaf nodes |
7171 | | - * inside an element with the wikiEditor-noinclude class. |
| 7227 | + * inside an element with the wikiEditor-noinclude class. This basically wraps rawTraverser |
7172 | 7228 | * |
7173 | 7229 | * Usage: |
7174 | 7230 | * var t = context.fn.traverser( context.$content ); |
— | — | @@ -7177,48 +7233,10 @@ |
7178 | 7234 | * // Trying to advance past the end will set t.node to null |
7179 | 7235 | */ |
7180 | 7236 | 'traverser': function( start ) { |
7181 | | - function Traverser( node, depth, inP ) { |
7182 | | - this.node = node; |
7183 | | - this.depth = depth; |
7184 | | - this.inP = inP; |
7185 | | - this.next = function() { |
7186 | | - var p = this.node; |
7187 | | - var nextDepth = this.depth; |
7188 | | - var nextInP = this.inP; |
7189 | | - while ( p && !p.nextSibling ) { |
7190 | | - if ( p.nodeName == "P" ) { |
7191 | | - nextInP = false; |
7192 | | - } |
7193 | | - p = p.parentNode; |
7194 | | - nextDepth--; |
7195 | | - if ( nextDepth == 0 ) { |
7196 | | - // We're back at the start node |
7197 | | - p = null; |
7198 | | - } |
7199 | | - } |
7200 | | - p = p ? p.nextSibling : null; |
7201 | | - do { |
7202 | | - // Filter nodes with the wikiEditor-noinclude class |
7203 | | - // Don't use $( p ).hasClass( 'wikiEditor-noinclude' ) because |
7204 | | - // $() is slow in a tight loop |
7205 | | - while ( p && ( ' ' + p.className + ' ' ).indexOf( ' wikiEditor-noinclude ' ) != -1 ) { |
7206 | | - p = p.nextSibling; |
7207 | | - } |
7208 | | - if ( p && p.firstChild ) { |
7209 | | - p = p.firstChild; |
7210 | | - nextDepth++; |
7211 | | - if ( p.nodeName == "P" ) { |
7212 | | - nextInP = true; |
7213 | | - } |
7214 | | - } |
7215 | | - } while ( p && p.firstChild ); |
7216 | | - return p ? new Traverser( p, nextDepth, nextInP ) : null; |
7217 | | - }; |
7218 | | - } |
7219 | 7237 | // Find the leftmost leaf node in the tree |
7220 | 7238 | var node = start.jquery ? start.get( 0 ) : start; |
7221 | 7239 | var depth = 0; |
7222 | | - var inP = node.nodeName == "P"; |
| 7240 | + var inP = node.nodeName == "P" ? node : null; |
7223 | 7241 | do { |
7224 | 7242 | // Filter nodes with the wikiEditor-noinclude class |
7225 | 7243 | // Don't use $( p ).hasClass( 'wikiEditor-noinclude' ) because |
— | — | @@ -7230,11 +7248,11 @@ |
7231 | 7249 | node = node.firstChild; |
7232 | 7250 | depth++; |
7233 | 7251 | if ( node.nodeName == "P" ) { |
7234 | | - inP = true; |
| 7252 | + inP = node; |
7235 | 7253 | } |
7236 | 7254 | } |
7237 | 7255 | } while ( node && node.firstChild ); |
7238 | | - return new Traverser( node, depth, inP ); |
| 7256 | + return new context.fn.rawTraverser( node, depth, inP ); |
7239 | 7257 | }, |
7240 | 7258 | 'getOffset': function( offset ) { |
7241 | 7259 | if ( !context.offsets ) { |
— | — | @@ -7273,13 +7291,13 @@ |
7274 | 7292 | var t = context.fn.traverser( context.$content ); |
7275 | 7293 | var pos = 0, lastTextNode = null, lastTextNodeDepth = null; |
7276 | 7294 | while ( t ) { |
7277 | | - if ( t.node.nodeName != '#text' && t.node.nodeName != 'BR' ) { |
| 7295 | + if ( t.node.nodeName != '#text' && t.node.nodeName != 'BR' && t.node.nodeName != 'P' ) { |
7278 | 7296 | t = t.next(); |
7279 | 7297 | continue; |
7280 | 7298 | } |
7281 | 7299 | var nextPos = t.node.nodeName == '#text' ? pos + t.node.nodeValue.length : pos + 1; |
7282 | 7300 | var nextT = t.next(); |
7283 | | - var leavingP = t.inP && nextT && !nextT.inP; |
| 7301 | + var leavingP = t.node.nodeName != 'P' && t.inP && nextT && ( !nextT.inP || nextT.inP != t.inP ); |
7284 | 7302 | context.offsets[pos] = { |
7285 | 7303 | 'node': t.node, |
7286 | 7304 | 'offset': 0, |
— | — | @@ -7784,23 +7802,27 @@ |
7785 | 7803 | } |
7786 | 7804 | var startNode = s.node; |
7787 | 7805 | var startDepth = s.depth; |
7788 | | - // The next marker starts somewhere in this textNode or at this BR |
7789 | | - if ( s.offset > 0 ) { |
7790 | | - // t.node must be a textnode at this point because |
7791 | | - // only textnodes can have offset > 0 |
7792 | | - |
7793 | | - // Split off the prefix |
7794 | | - // This leaves the prefix in the current node and puts |
7795 | | - // the rest in a new node which is our start node |
7796 | | - startNode = startNode.splitText( s.offset ); |
7797 | | - } |
| 7806 | + |
7798 | 7807 | // Don't wrap leading BRs, produces undesirable results |
7799 | | - while ( startNode.nodeName == 'BR' ) { |
| 7808 | + // FIXME: It's also possible that the offset is a bit high because getOffset() has incremented |
| 7809 | + // .length to fake the newline caused by startNode being in a P. In this case, prevent |
| 7810 | + // the textnode splitting below from making startNode an empty textnode, IE barfs on that |
| 7811 | + while ( startNode.nodeName == 'BR' || s.offset == startNode.nodeValue.length ) { |
7800 | 7812 | start++; |
7801 | 7813 | s = context.fn.getOffset( start ); |
7802 | 7814 | startNode = s.node; |
7803 | 7815 | startDepth = s.depth; |
7804 | 7816 | } |
| 7817 | + |
| 7818 | + // The next marker starts somewhere in this textNode or at this BR |
| 7819 | + if ( s.offset > 0 && s.node.nodeName == '#text' ) { |
| 7820 | + // Split off the prefix |
| 7821 | + // This leaves the prefix in the current node and puts |
| 7822 | + // the rest in a new node which is our start node |
| 7823 | + startNode = startNode.splitText( s.offset ); |
| 7824 | + // This also invalidates cached offset objects |
| 7825 | + context.fn.purgeOffsets(); // TODO: Optimize better, get end offset object earlier |
| 7826 | + } |
7805 | 7827 | |
7806 | 7828 | var end = markers[i].end; |
7807 | 7829 | var e = context.fn.getOffset( end ); |
— | — | @@ -7810,14 +7832,13 @@ |
7811 | 7833 | } |
7812 | 7834 | var endNode = e.node; |
7813 | 7835 | var endDepth = e.depth; |
7814 | | - if ( e.offset < e.length - 1 ) { |
7815 | | - // t.node must be a textnode at this point because |
7816 | | - // .length is 1 for BRs and offset can't be < 0 |
7817 | | - |
| 7836 | + if ( e.offset < e.length - 1 && e.node.nodeName == '#text' ) { |
7818 | 7837 | // Split off the suffix - This puts the suffix in a new node and leaves the rest in the current |
7819 | 7838 | // node. |
7820 | 7839 | // endNode.nodeValue.length - ( newPos - markers[i].end ) |
7821 | 7840 | endNode.splitText( e.offset + 1 ); |
| 7841 | + // This also invalidates cached offset objects |
| 7842 | + context.fn.purgeOffsets(); // TODO: Optimize better, get end offset object earlier |
7822 | 7843 | } |
7823 | 7844 | |
7824 | 7845 | // Don't wrap trailing BRs, doing that causes weird issues |
— | — | @@ -7857,13 +7878,19 @@ |
7858 | 7879 | ca1 = ca1.parentNode.firstChild == ca1 ? ca1.parentNode : null; |
7859 | 7880 | ca2 = ca2.parentNode.lastChild == ca2 ? ca2.parentNode : null; |
7860 | 7881 | } |
7861 | | - if ( ca1 && ca2 && ca1.parentNode && ca2.nextSibling ) { |
| 7882 | + if ( ca1 && ca2 && ca1.parentNode ) { |
7862 | 7883 | var anchor = markers[i].getAnchor( ca1, ca2 ); |
7863 | 7884 | if ( !anchor ) { |
7864 | 7885 | // We have to store things like .parentNode and .nextSibling because appendChild() changes these |
7865 | 7886 | // properties |
7866 | 7887 | var newNode = ca1.ownerDocument.createElement( 'div' ); |
7867 | 7888 | var commonAncestor = ca1.parentNode; |
| 7889 | + // Special case: can't put block elements in a <p> |
| 7890 | + if ( commonAncestor.nodeName == 'P' && commonAncestor.parentNode ) { |
| 7891 | + commonAncestor = commonAncestor.parentNode; |
| 7892 | + ca1 = ca1.parentNode; |
| 7893 | + ca2 = ca2.parentNode; |
| 7894 | + } |
7868 | 7895 | var nextNode = ca2.nextSibling; |
7869 | 7896 | if ( markers[i].anchor == 'wrap' ) { |
7870 | 7897 | // Append all nodes between ca1 and ca2 (inclusive) to newNode |
Index: trunk/extensions/UsabilityInitiative/js/plugins.combined.min.js |
— | — | @@ -450,7 +450,9 @@ |
451 | 451 | if(!context.$tabs.children().size()){addTab({'name':'wikitext','titleMsg':'wikieditor-wikitext-tab'});} |
452 | 452 | addTab(options);return $('<div></div>').addClass('wikiEditor-ui-view wikiEditor-ui-view-'+options.name).hide().appendTo(context.$ui);},'htmlToText':function(html){var $pre=$('<pre>'+ |
453 | 453 | html.replace(/\r?\n/g,"").replace(/ /g," ") |
454 | | -+'</pre>');$pre.find('.wikiEditor-noinclude').each(function(){$(this).remove();});$pre.find('.wikiEditor-tab').each(function(){$(this).text("\t")});$pre.find('br').each(function(){$(this).replaceWith("\n");});$pre.find('p').each(function(){if(this.previousSibling||this.parentNode!=$pre.get(0)){$(this).text("\n"+$(this).text());}});return $pre.text();},'getContents':function(){return context.fn.htmlToText(context.$content.html());},'getSelection':function(){var retval;if(context.$iframe[0].contentWindow.getSelection){retval=context.$iframe[0].contentWindow.getSelection();}else if(context.$iframe[0].contentWindow.document.selection){retval=context.$iframe[0].contentWindow.document.selection.createRange();} |
| 454 | ++'</pre>');$pre.find('.wikiEditor-noinclude').each(function(){$(this).remove();});$pre.find('.wikiEditor-tab').each(function(){$(this).text("\t")});$pre.find('br').each(function(){$(this).replaceWith("\n");});$pre.find('p').each(function(){if(this.previousSibling||this.parentNode!=$pre.get(0)){var text="\n"+$(this).text();var t=new context.fn.rawTraverser(this.lastChild,-10,this).next();while(t&&t.node.nodeName!='#text'&&t.node.nodeName!='BR'&&t.node.nodeName!='P'){t=t.next();} |
| 455 | +if(t&&!t.inP&&t.node.nodeName!='P'){text+="\n";} |
| 456 | +$(this).text(text);}});return $pre.text();},'getContents':function(){return context.fn.htmlToText(context.$content.html());},'getSelection':function(){var retval;if(context.$iframe[0].contentWindow.getSelection){retval=context.$iframe[0].contentWindow.getSelection();}else if(context.$iframe[0].contentWindow.document.selection){retval=context.$iframe[0].contentWindow.document.selection.createRange();} |
455 | 457 | if(typeof retval.text!='undefined'){retval=context.fn.htmlToText(retval.htmlText);}else if(retval.toString){retval=retval.toString();} |
456 | 458 | return retval;},'encapsulateSelection':function(options){var selText=$(this).textSelection('getSelection');var selTextArr;var selectAfter=false;var pre=options.pre,post=options.post;if(!selText){selText=options.peri;selectAfter=true;}else if(options.replace){selText=options.peri;}else if(selText.charAt(selText.length-1)==' '){selText=selText.substring(0,selText.length-1);post+=' ';} |
457 | 459 | if(options.splitlines){selTextArr=selText.split(/\n/);} |
— | — | @@ -484,18 +486,18 @@ |
485 | 487 | var next=e.previousSibling;while(next&&next.lastChild){next=next.lastChild;} |
486 | 488 | e=next||e.parentNode;strict=false;} |
487 | 489 | if(selector!='*') |
488 | | -occurrences.removeClass('wikiEditor-beforeSelection-tagged');return retval||$([]);},'traverser':function(start){function Traverser(node,depth,inP){this.node=node;this.depth=depth;this.inP=inP;this.next=function(){var p=this.node;var nextDepth=this.depth;var nextInP=this.inP;while(p&&!p.nextSibling){if(p.nodeName=="P"){nextInP=false;} |
489 | | -p=p.parentNode;nextDepth--;if(nextDepth==0){p=null;}} |
490 | | -p=p?p.nextSibling:null;do{while(p&&(' '+p.className+' ').indexOf(' wikiEditor-noinclude ')!=-1){p=p.nextSibling;} |
491 | | -if(p&&p.firstChild){p=p.firstChild;nextDepth++;if(p.nodeName=="P"){nextInP=true;}}}while(p&&p.firstChild);return p?new Traverser(p,nextDepth,nextInP):null;};} |
492 | | -var node=start.jquery?start.get(0):start;var depth=0;var inP=node.nodeName=="P";do{while(node&&(' '+node.className+' ').indexOf(' wikiEditor-noinclude ')!=-1){node=node.nextSibling;} |
493 | | -if(node&&node.firstChild){node=node.firstChild;depth++;if(node.nodeName=="P"){inP=true;}}}while(node&&node.firstChild);return new Traverser(node,depth,inP);},'getOffset':function(offset){if(!context.offsets){context.fn.refreshOffsets();} |
| 490 | +occurrences.removeClass('wikiEditor-beforeSelection-tagged');return retval||$([]);},'rawTraverser':function(node,depth,inP){this.node=node;this.depth=depth;this.inP=inP;this.next=function(){var p=this.node;var nextDepth=this.depth;var nextInP=this.inP;while(p&&!p.nextSibling){p=p.parentNode;nextDepth--;if(nextDepth==0){p=null;} |
| 491 | +if(p&&p.nodeName=="P"){nextInP=null;}} |
| 492 | +p=p?p.nextSibling:null;if(p&&p.nodeName=="P"){nextInP=p;} |
| 493 | +do{while(p&&(' '+p.className+' ').indexOf(' wikiEditor-noinclude ')!=-1){p=p.nextSibling;} |
| 494 | +if(p&&p.firstChild){p=p.firstChild;nextDepth++;if(p.nodeName=="P"){nextInP=p;}}}while(p&&p.firstChild);return p?new context.fn.rawTraverser(p,nextDepth,nextInP):null;};},'traverser':function(start){var node=start.jquery?start.get(0):start;var depth=0;var inP=node.nodeName=="P"?node:null;do{while(node&&(' '+node.className+' ').indexOf(' wikiEditor-noinclude ')!=-1){node=node.nextSibling;} |
| 495 | +if(node&&node.firstChild){node=node.firstChild;depth++;if(node.nodeName=="P"){inP=node;}}}while(node&&node.firstChild);return new context.fn.rawTraverser(node,depth,inP);},'getOffset':function(offset){if(!context.offsets){context.fn.refreshOffsets();} |
494 | 496 | if(offset in context.offsets){return context.offsets[offset];} |
495 | 497 | var lowerBound=-1;for(var o in context.offsets){if(o>offset){break;} |
496 | 498 | lowerBound=o;} |
497 | 499 | if(!(lowerBound in context.offsets)){return null;} |
498 | | -var base=context.offsets[lowerBound];return context.offsets[offset]={'node':base.node,'offset':base.offset+offset-lowerBound,'length':base.length,'depth':base.depth,'lastTextNode':base.lastTextNode,'lastTextNodeDepth':base.lastTextNodeDepth};},'purgeOffsets':function(){context.offsets=null;},'refreshOffsets':function(){context.offsets=[];var t=context.fn.traverser(context.$content);var pos=0,lastTextNode=null,lastTextNodeDepth=null;while(t){if(t.node.nodeName!='#text'&&t.node.nodeName!='BR'){t=t.next();continue;} |
499 | | -var nextPos=t.node.nodeName=='#text'?pos+t.node.nodeValue.length:pos+1;var nextT=t.next();var leavingP=t.inP&&nextT&&!nextT.inP;context.offsets[pos]={'node':t.node,'offset':0,'length':nextPos-pos+(leavingP?1:0),'depth':t.depth,'lastTextNode':lastTextNode,'lastTextNodeDepth':lastTextNodeDepth};if(leavingP){context.offsets[nextPos]={'node':t.node,'offset':nextPos-pos,'length':nextPos-pos+1,'depth':t.depth,'lastTextNode':lastTextNode,'lastTextNodeDepth':lastTextNodeDepth};} |
| 500 | +var base=context.offsets[lowerBound];return context.offsets[offset]={'node':base.node,'offset':base.offset+offset-lowerBound,'length':base.length,'depth':base.depth,'lastTextNode':base.lastTextNode,'lastTextNodeDepth':base.lastTextNodeDepth};},'purgeOffsets':function(){context.offsets=null;},'refreshOffsets':function(){context.offsets=[];var t=context.fn.traverser(context.$content);var pos=0,lastTextNode=null,lastTextNodeDepth=null;while(t){if(t.node.nodeName!='#text'&&t.node.nodeName!='BR'&&t.node.nodeName!='P'){t=t.next();continue;} |
| 501 | +var nextPos=t.node.nodeName=='#text'?pos+t.node.nodeValue.length:pos+1;var nextT=t.next();var leavingP=t.node.nodeName!='P'&&t.inP&&nextT&&(!nextT.inP||nextT.inP!=t.inP);context.offsets[pos]={'node':t.node,'offset':0,'length':nextPos-pos+(leavingP?1:0),'depth':t.depth,'lastTextNode':lastTextNode,'lastTextNodeDepth':lastTextNodeDepth};if(leavingP){context.offsets[nextPos]={'node':t.node,'offset':nextPos-pos,'length':nextPos-pos+1,'depth':t.depth,'lastTextNode':lastTextNode,'lastTextNodeDepth':lastTextNodeDepth};} |
500 | 502 | pos=nextPos+(leavingP?1:0);if(t.node.nodeName=='#text'){lastTextNode=t.node;lastTextNodeDepth=t.depth;} |
501 | 503 | t=nextT;}}};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;}} |
502 | 504 | context.$content=$(context.$iframe[0].contentWindow.document.body);var html=context.$textarea.val().replace(/ /g,'&nbsp;').replace(/\<br\>/g,'<br>').replace(/\<span class="wikiEditor-tab"\>\<\/span\>/g,'<span class="wikiEditor-tab"></span>');if($.browser.msie){if($.browser.versionNumber<=7){html=html.replace(/ /g," ");}else{html=html.replace(/(^|\n) /g,"$1 ");} |
— | — | @@ -513,15 +515,16 @@ |
514 | 516 | 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;} |
515 | 517 | 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);}}}} |
516 | 518 | 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;});for(var i=0;i<markers.length;i++){var start=markers[i].start;var s=context.fn.getOffset(start);if(!s){continue;} |
517 | | -var startNode=s.node;var startDepth=s.depth;if(s.offset>0){startNode=startNode.splitText(s.offset);} |
518 | | -while(startNode.nodeName=='BR'){start++;s=context.fn.getOffset(start);startNode=s.node;startDepth=s.depth;} |
| 519 | +var startNode=s.node;var startDepth=s.depth;while(startNode.nodeName=='BR'||s.offset==startNode.nodeValue.length){start++;s=context.fn.getOffset(start);startNode=s.node;startDepth=s.depth;} |
| 520 | +if(s.offset>0&&s.node.nodeName=='#text'){startNode=startNode.splitText(s.offset);context.fn.purgeOffsets();} |
519 | 521 | var end=markers[i].end;var e=context.fn.getOffset(end);if(!e){continue;} |
520 | | -var endNode=e.node;var endDepth=e.depth;if(e.offset<e.length-1){endNode.splitText(e.offset+1);} |
| 522 | +var endNode=e.node;var endDepth=e.depth;if(e.offset<e.length-1&&e.node.nodeName=='#text'){endNode.splitText(e.offset+1);context.fn.purgeOffsets();} |
521 | 523 | if(endNode.nodeName=='BR'){endNode=e.lastTextNode;endDepth=e.lastTextNodeDepth;} |
522 | 524 | 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;}} |
523 | 525 | else if(startDepth<endDepth){for(var j=0;j<endDepth-startDepth&&ca2;j++){ca2=ca2.parentNode.lastChild==ca2?ca2.parentNode:null;}} |
524 | 526 | while(ca1&&ca2&&ca1.parentNode&&ca2.parentNode&&ca1.parentNode!=ca2.parentNode&&ca1.parentNode.firstChild&&ca2.parentNode.lastChild){ca1=ca1.parentNode.firstChild==ca1?ca1.parentNode:null;ca2=ca2.parentNode.lastChild==ca2?ca2.parentNode:null;} |
525 | | -if(ca1&&ca2&&ca1.parentNode&&ca2.nextSibling){var anchor=markers[i].getAnchor(ca1,ca2);if(!anchor){var newNode=ca1.ownerDocument.createElement('div');var commonAncestor=ca1.parentNode;var nextNode=ca2.nextSibling;if(markers[i].anchor=='wrap'){var n=ca1;while(n!=nextNode){var ns=n.nextSibling;newNode.appendChild(n);n=ns;} |
| 527 | +if(ca1&&ca2&&ca1.parentNode){var anchor=markers[i].getAnchor(ca1,ca2);if(!anchor){var newNode=ca1.ownerDocument.createElement('div');var commonAncestor=ca1.parentNode;if(commonAncestor.nodeName=='P'&&commonAncestor.parentNode){commonAncestor=commonAncestor.parentNode;ca1=ca1.parentNode;ca2=ca2.parentNode;} |
| 528 | +var nextNode=ca2.nextSibling;if(markers[i].anchor=='wrap'){var n=ca1;while(n!=nextNode){var ns=n.nextSibling;newNode.appendChild(n);n=ns;} |
526 | 529 | 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);}} |
527 | 530 | $(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);}}} |
528 | 531 | context.$content.find('div.wikiEditor-highlight:not(.wikiEditor-highlight-tmp)').each(function(){if($(this).data('marker')&&typeof $(this).data('marker').unwrap=='function') |