r69165 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r69164‎ | r69165 | r69166 >
Date:23:26, 7 July 2010
Author:adam
Status:resolved (Comments)
Tags:
Comment:
Adding a new plugin to handle match highlighting better in simplesearch. First pass; still needs work; feedback welcome.
Modified paths:
  • /trunk/extensions/UsabilityInitiative/UsabilityInitiative.hooks.php (modified) (history)
  • /trunk/extensions/UsabilityInitiative/css/combined.css (modified) (history)
  • /trunk/extensions/UsabilityInitiative/css/combined.min.css (modified) (history)
  • /trunk/extensions/UsabilityInitiative/css/suggestions.css (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.autoEllipsis.js (modified) (history)
  • /trunk/extensions/UsabilityInitiative/js/plugins/jquery.highlightText.js (added) (history)
  • /trunk/extensions/UsabilityInitiative/js/plugins/jquery.suggestions.js (modified) (history)

Diff [purge]

Index: trunk/extensions/UsabilityInitiative/css/suggestions.css
@@ -62,6 +62,7 @@
6363 color: white;
6464 color: HighlightText;
6565 }
66 -.autoellipsis-matched {
 66+.autoellipsis-matched,
 67+.highlight {
6768 font-weight: bold;
6869 }
\ No newline at end of file
Index: trunk/extensions/UsabilityInitiative/css/combined.css
@@ -62,7 +62,8 @@
6363 color: white;
6464 color: HighlightText;
6565 }
66 -.autoellipsis-matched {
 66+.autoellipsis-matched,
 67+.highlight {
6768 font-weight: bold;
6869 }/* Prototype code to show collapsing left nav options */
6970 #mw-panel.collapsible-nav div.portal {
Index: trunk/extensions/UsabilityInitiative/css/combined.min.css
@@ -60,7 +60,8 @@
6161 color:white;
6262 color:HighlightText;
6363 }
64 -.autoellipsis-matched{
 64+.autoellipsis-matched,
 65+.highlight{
6566 font-weight:bold;
6667 }
6768 #mw-panel.collapsible-nav div.portal{
Index: trunk/extensions/UsabilityInitiative/UsabilityInitiative.hooks.php
@@ -31,11 +31,11 @@
3232 array( 'src' => 'css/vector/jquery-ui-1.7.2.css', 'version' => '1.7.2y' ),
3333 ),
3434 'combined' => array(
35 - array( 'src' => 'css/combined.css', 'version' => 114 ),
 35+ array( 'src' => 'css/combined.css', 'version' => 115 ),
3636 array( 'src' => 'css/vector/jquery-ui-1.7.2.css', 'version' => '1.7.2y' ),
3737 ),
3838 'minified' => array(
39 - array( 'src' => 'css/combined.min.css', 'version' => 114 ),
 39+ array( 'src' => 'css/combined.min.css', 'version' => 115 ),
4040 array( 'src' => 'css/vector/jquery-ui-1.7.2.css', 'version' => '1.7.2y' ),
4141 ),
4242 )
@@ -58,13 +58,14 @@
5959
6060 // Core functionality of extension scripts
6161 array( 'src' => 'js/plugins/jquery.async.js', 'version' => 3 ),
62 - array( 'src' => 'js/plugins/jquery.autoEllipsis.js', 'version' => 16 ),
 62+ array( 'src' => 'js/plugins/jquery.autoEllipsis.js', 'version' => 17 ),
6363 array( 'src' => 'js/plugins/jquery.browser.js', 'version' => 9 ),
6464 array( 'src' => 'js/plugins/jquery.collapsibleTabs.js', 'version' => 6 ),
6565 array( 'src' => 'js/plugins/jquery.color.js', 'version' => 1 ),
6666 array( 'src' => 'js/plugins/jquery.cookie.js', 'version' => 4 ),
6767 array( 'src' => 'js/plugins/jquery.delayedBind.js', 'version' => 1 ),
6868 array( 'src' => 'js/plugins/jquery.expandableField.js', 'version' => 17 ),
 69+ array( 'src' => 'js/plugins/jquery.highlightText.js', 'version' => 1 ),
6970 array( 'src' => 'js/plugins/jquery.suggestions.js', 'version' => 32 ),
7071 array( 'src' => 'js/plugins/jquery.textSelection.js', 'version' => 36 ),
7172 array( 'src' => 'js/plugins/jquery.wikiEditor.js', 'version' => 196 ),
@@ -82,10 +83,10 @@
8384 array( 'src' => 'js/thirdparty/contentCollector.js', 'version' => 2 ),
8485 ),
8586 'combined' => array(
86 - array( 'src' => 'js/plugins.combined.js', 'version' => 449 ),
 87+ array( 'src' => 'js/plugins.combined.js', 'version' => 450 ),
8788 ),
8889 'minified' => array(
89 - array( 'src' => 'js/plugins.combined.min.js', 'version' => 460 ),
 90+ array( 'src' => 'js/plugins.combined.min.js', 'version' => 461 ),
9091 ),
9192 ),
9293 );
Index: trunk/extensions/UsabilityInitiative/js/plugins/jquery.suggestions.js
@@ -167,7 +167,7 @@
168168 } else {
169169 // Add <span> with text
170170 if( context.config.highlightInput ) {
171 - matchedText = text.substr( 0, context.data.prevText.length );
 171+ matchedText = context.data.prevText;
172172 }
173173 $result.append( $( '<span />' )
174174 .css( 'whiteSpace', 'nowrap' )
Index: trunk/extensions/UsabilityInitiative/js/plugins/jquery.highlightText.js
@@ -0,0 +1,57 @@
 2+/**
 3+ * Plugin that highlights matched word partials in a given element
 4+ * TODO: add a function for restoring the previous text
 5+ * TODO:
 6+ */
 7+( function( $ ) {
 8+
 9+$.highlightText = {
 10+
 11+ // Split our pattern string at spaces and run our highlight function on the results
 12+ splitAndHighlight: function( node, pat ) {
 13+ var patArray = pat.split(" ");
 14+ for ( var i = 0; i < patArray.length; i++ ) {
 15+ if ( patArray[i].length == 0 ) continue;
 16+ $.highlightText.innerHighlight( node, patArray[i] );
 17+ }
 18+ return node;
 19+ },
 20+ // adapted from Johann Burkard's highlight plugin
 21+ // http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html
 22+ // scans a node looking for the pattern and wraps a span around each match
 23+ innerHighlight: function( node, pat ) {
 24+ var skip = 0;
 25+ if ( node.nodeType == 3 ) {
 26+ // TODO - need to be smarter about the character matching here.
 27+ // non latin characters can make regex think a new word has begun.
 28+ var pos = node.data.search( new RegExp( "\\b" + RegExp.escape( pat ), "i" ) );
 29+ if ( pos >= 0 ) {
 30+ var spannode = document.createElement( 'span' );
 31+ spannode.className = 'highlight';
 32+ var middlebit = node.splitText( pos );
 33+ var endbit = middlebit.splitText( pat.length );
 34+ var middleclone = middlebit.cloneNode( true );
 35+ spannode.appendChild( middleclone );
 36+ middlebit.parentNode.replaceChild( spannode, middlebit );
 37+ skip = 1;
 38+ }
 39+ } else if ( node.nodeType == 1 && node.childNodes && !/(script|style)/i.test( node.tagName )
 40+ && !( node.tagName.toLowerCase() == 'span' && node.classList.contains( 'highlight' ) ) ) {
 41+ for ( var i = 0; i < node.childNodes.length; ++i ) {
 42+ i += $.highlightText.innerHighlight( node.childNodes[i], pat );
 43+ }
 44+ }
 45+ return skip;
 46+ }
 47+};
 48+
 49+$.fn.highlightText = function( matchString ) {
 50+ return $( this ).each( function() {
 51+ var $this = $( this );
 52+ $this.data( 'highlightText', { originalText: $this.text() } );
 53+ $.highlightText.splitAndHighlight( this, matchString );
 54+ } );
 55+};
 56+
 57+} )( jQuery );
 58+
Index: trunk/extensions/UsabilityInitiative/js/plugins/jquery.autoEllipsis.js
@@ -33,32 +33,15 @@
3434 // protected text element - the width of this element is counted, but next is never trimmed from it
3535 var $protectedText = null;
3636
37 - if ( options.matchText ) {
38 - var text = $this.text();
39 - var matchedText = options.matchText;
40 - $trimmableText = $( '<span />' )
 37+ if ( options.hasSpan ) {
 38+ $trimmableText = $this.children( options.selector );
 39+ } else {
 40+ $trimmableText = $( '<span />' )
4141 .css( 'whiteSpace', 'nowrap' )
42 - .addClass( 'autoellipsis-trimmed' )
43 - .text( $this.text().substr( matchedText.length, $this.text().length ) );
44 - $protectedText = $( '<span />' )
45 - .addClass( 'autoellipsis-matched' )
46 - .css( 'whiteSpace', 'nowrap' )
47 - .text( options.matchText );
48 - $container
 42+ .text( $this.text() );
 43+ $this
4944 .empty()
50 - .append( $protectedText )
5145 .append( $trimmableText );
52 - } else {
53 - if ( options.hasSpan ) {
54 - $trimmableText = $this.children( options.selector );
55 - } else {
56 - $trimmableText = $( '<span />' )
57 - .css( 'whiteSpace', 'nowrap' )
58 - .text( $this.text() );
59 - $this
60 - .empty()
61 - .append( $trimmableText );
62 - }
6346 }
6447
6548 var text = $container.text();
@@ -136,6 +119,7 @@
137120 $container.attr( 'title', text );
138121 }
139122 if ( options.matchText ) {
 123+ $container.highlightText( options.matchText );
140124 matchTextCache[text][options.matchText][w] = $container.html();
141125 } else {
142126 cache[text][w] = $container.html();
Index: trunk/extensions/UsabilityInitiative/js/plugins.combined.js
@@ -245,32 +245,15 @@
246246 // protected text element - the width of this element is counted, but next is never trimmed from it
247247 var $protectedText = null;
248248
249 - if ( options.matchText ) {
250 - var text = $this.text();
251 - var matchedText = options.matchText;
252 - $trimmableText = $( '<span />' )
 249+ if ( options.hasSpan ) {
 250+ $trimmableText = $this.children( options.selector );
 251+ } else {
 252+ $trimmableText = $( '<span />' )
253253 .css( 'whiteSpace', 'nowrap' )
254 - .addClass( 'autoellipsis-trimmed' )
255 - .text( $this.text().substr( matchedText.length, $this.text().length ) );
256 - $protectedText = $( '<span />' )
257 - .addClass( 'autoellipsis-matched' )
258 - .css( 'whiteSpace', 'nowrap' )
259 - .text( options.matchText );
260 - $container
 254+ .text( $this.text() );
 255+ $this
261256 .empty()
262 - .append( $protectedText )
263257 .append( $trimmableText );
264 - } else {
265 - if ( options.hasSpan ) {
266 - $trimmableText = $this.children( options.selector );
267 - } else {
268 - $trimmableText = $( '<span />' )
269 - .css( 'whiteSpace', 'nowrap' )
270 - .text( $this.text() );
271 - $this
272 - .empty()
273 - .append( $trimmableText );
274 - }
275258 }
276259
277260 var text = $container.text();
@@ -348,6 +331,7 @@
349332 $container.attr( 'title', text );
350333 }
351334 if ( options.matchText ) {
 335+ $container.highlightText( options.matchText );
352336 matchTextCache[text][options.matchText][w] = $container.html();
353337 } else {
354338 cache[text][w] = $container.html();
@@ -1145,7 +1129,7 @@
11461130 } else {
11471131 // Add <span> with text
11481132 if( context.config.highlightInput ) {
1149 - matchedText = text.substr( 0, context.data.prevText.length );
 1133+ matchedText = context.data.prevText;
11501134 }
11511135 $result.append( $( '<span />' )
11521136 .css( 'whiteSpace', 'nowrap' )
Index: trunk/extensions/UsabilityInitiative/js/plugins.combined.min.js
@@ -20,7 +20,7 @@
2121 {var val=array[i];return loop.call(val,i++,val);}}));}
2222 $.fn.eachAsync=function(opts)
2323 {$.eachAsync(this,opts);return this;}})(jQuery);(function($){var cache={};var matchTextCache={};$.fn.autoEllipsis=function(options){options=$.extend({'position':'center','tooltip':false,'restoreText':false,'hasSpan':false,'matchText':null},options);$(this).each(function(){var $this=$(this);if(options.restoreText){if(!$this.data('autoEllipsis.originalText')){$this.data('autoEllipsis.originalText',$this.text());}else{$this.text($this.data('autoEllipsis.originalText'));}}
24 -var $container=$this;var $trimmableText=null;var $protectedText=null;if(options.matchText){var text=$this.text();var matchedText=options.matchText;$trimmableText=$('<span />').css('whiteSpace','nowrap').addClass('autoellipsis-trimmed').text($this.text().substr(matchedText.length,$this.text().length));$protectedText=$('<span />').addClass('autoellipsis-matched').css('whiteSpace','nowrap').text(options.matchText);$container.empty().append($protectedText).append($trimmableText);}else{if(options.hasSpan){$trimmableText=$this.children(options.selector);}else{$trimmableText=$('<span />').css('whiteSpace','nowrap').text($this.text());$this.empty().append($trimmableText);}}
 24+var $container=$this;var $trimmableText=null;var $protectedText=null;if(options.hasSpan){$trimmableText=$this.children(options.selector);}else{$trimmableText=$('<span />').css('whiteSpace','nowrap').text($this.text());$this.empty().append($trimmableText);}
2525 var text=$container.text();var trimmableText=$trimmableText.text();var w=$container.width();var pw=$protectedText?$protectedText.width():0;if(!(text in cache)){cache[text]={};}
2626 if(options.matchText&&!(text in matchTextCache)){matchTextCache[text]={};}
2727 if(options.matchText&&!(options.matchText in matchTextCache[text])){matchTextCache[text][options.matchText]={};}
@@ -32,7 +32,7 @@
3333 break;case'left':var r=0;while($trimmableText.outerWidth()+pw>w&&r<trimmableText.length){$trimmableText.text('...'+trimmableText.substr(r));r++;}
3434 break;}}
3535 if(options.tooltip){$container.attr('title',text);}
36 -if(options.matchText){matchTextCache[text][options.matchText][w]=$container.html();}else{cache[text][w]=$container.html();}});};})(jQuery);(function($){$.browserTest=function(a,z){var u='unknown',x='X',m=function(r,h){for(var i=0;i<h.length;i=i+1){r=r.replace(h[i][0],h[i][1]);}
 36+if(options.matchText){$container.highlightText(options.matchText);matchTextCache[text][options.matchText][w]=$container.html();}else{cache[text][w]=$container.html();}});};})(jQuery);(function($){$.browserTest=function(a,z){var u='unknown',x='X',m=function(r,h){for(var i=0;i<h.length;i=i+1){r=r.replace(h[i][0],h[i][1]);}
3737 return r;},c=function(i,a,b,c){var r={name:m((a.exec(i)||[u,u])[1],b)};r[r.name]=true;r.version=(c.exec(i)||[x,x,x,x])[3];if(r.name.match(/safari/)&&r.version>400){r.version='2.0';}
3838 if(r.name==='presto'){r.version=($.browser.version>9.27)?'futhark':'linear_b';}
3939 if(r.name==='opera'&&$.browser.version>=9.8){r.version=i.match(/version\/([0-9\.]*)/i)[1]||10;}
@@ -64,7 +64,7 @@
6565 if(delayed){context.data.timerID=setTimeout(maybeFetch,context.config.delay);}else{maybeFetch();}
6666 $.suggestions.special(context);},special:function(context){if(typeof context.config.special.render=='function'){setTimeout(function(){$special=context.data.$container.find('.suggestions-special');context.config.special.render.call($special,context.data.$textbox.val());},1);}},configure:function(context,property,value){switch(property){case'fetch':case'cancel':case'special':case'result':case'$region':context.config[property]=value;break;case'suggestions':context.config[property]=value;if(typeof context.data!=='undefined'){if(context.data.$textbox.val().length==0){context.data.$container.hide();}else{context.data.$container.show();var newCSS={'top':context.config.$region.offset().top+context.config.$region.outerHeight(),'bottom':'auto','width':context.config.$region.outerWidth(),'height':'auto'}
6767 if(context.config.positionFromLeft){newCSS['left']=context.config.$region.offset().left;newCSS['right']='auto';}else{newCSS['left']='auto';newCSS['right']=$('body').width()-(context.config.$region.offset().left+context.config.$region.outerWidth());}
68 -context.data.$container.css(newCSS);var $results=context.data.$container.children('.suggestions-results');$results.empty();var expWidth=-1;var $autoEllipseMe=$([]);var matchedText=null;for(var i=0;i<context.config.suggestions.length;i++){var text=context.config.suggestions[i];var $result=$('<div />').addClass('suggestions-result').attr('rel',i).data('text',context.config.suggestions[i]).mousemove(function(e){context.data.selectedWithMouse=true;$.suggestions.highlight(context,$(this).closest('.suggestions-results div'),false);}).appendTo($results);if(typeof context.config.result.render=='function'){context.config.result.render.call($result,context.config.suggestions[i]);}else{if(context.config.highlightInput){matchedText=text.substr(0,context.data.prevText.length);}
 68+context.data.$container.css(newCSS);var $results=context.data.$container.children('.suggestions-results');$results.empty();var expWidth=-1;var $autoEllipseMe=$([]);var matchedText=null;for(var i=0;i<context.config.suggestions.length;i++){var text=context.config.suggestions[i];var $result=$('<div />').addClass('suggestions-result').attr('rel',i).data('text',context.config.suggestions[i]).mousemove(function(e){context.data.selectedWithMouse=true;$.suggestions.highlight(context,$(this).closest('.suggestions-results div'),false);}).appendTo($results);if(typeof context.config.result.render=='function'){context.config.result.render.call($result,context.config.suggestions[i]);}else{if(context.config.highlightInput){matchedText=context.data.prevText;}
6969 $result.append($('<span />').css('whiteSpace','nowrap').text(text));var $span=$result.children('span');if($span.outerWidth()>$result.width()&&$span.outerWidth()>expWidth){expWidth=$span.outerWidth()+(context.data.$container.width()-$span.parent().width());}
7070 $autoEllipseMe=$autoEllipseMe.add($result);}}
7171 if(expWidth>context.data.$container.width()){var maxWidth=context.config.maxExpandFactor*context.data.$textbox.width();context.data.$container.width(Math.min(expWidth,maxWidth));}

Follow-up revisions

RevisionCommit summaryAuthorDate
r69483Follow up to r69165. Removing a use of the classlist apiadam17:23, 17 July 2010
r69564Follow up to r69165. Adding better comments and trimming out some unnecissary...adam19:44, 19 July 2010

Comments

#Comment by Adammiller~mediawikiwiki (talk | contribs)   17:02, 17 July 2010

whoops...apparently the classlist api is only supported in firefox at this point. Will have a fix in a bit.

#Comment by Catrope (talk | contribs)   15:41, 19 July 2010
+		if ( node.nodeType == 3 ) {
+			// TODO - need to be smarter about the character matching here. 
+			// non latin characters can make regex think a new word has begun. 
+			var pos = node.data.search( new RegExp( "\\b" + RegExp.escape( pat ), "i" ) );
+			if ( pos >= 0 ) {
+				var spannode = document.createElement( 'span' );
+				spannode.className = 'highlight';
+				var middlebit = node.splitText( pos );
+				var endbit = middlebit.splitText( pat.length );
+				var middleclone = middlebit.cloneNode( true );
+				spannode.appendChild( middleclone );
+				middlebit.parentNode.replaceChild( spannode, middlebit );
+				skip = 1;
+			}
+		} else if ( node.nodeType == 1 && node.childNodes && !/(script|style)/i.test( node.tagName )
+				&& !( node.tagName.toLowerCase() == 'span' && node.classList.contains( 'highlight' ) ) ) {
+			for ( var i = 0; i < node.childNodes.length; ++i ) {
+				i += $.highlightText.innerHighlight( node.childNodes[i], pat );
+			}
+		}

Instead of using all those DOM functions, you could just use jQuery functions; you're in a jQuery plugin after all.

#Comment by Adammiller~mediawikiwiki (talk | contribs)   19:45, 19 July 2010

Retaining all the DOM functions since I want to deal with textnodes. Added better comments on all this code in r69564.

Status & tagging log