Open main menu

CoolCornucopia

Joined 18 September 2011

Hi!
Welcome to my homepage where I am happy to share with you small pieces of codes
to improve the wonderful Mediawiki engine and its extensions...

Contents

ods2wiki (also named xls2wiki)Edit

I did a script to convert tables from Microsoft Excel (ods format), OpenOffice or LibreOffice documents to the MediaWiki syntax. I know such scripts are already available here and here and I know LibreOffice embbeds a wiki converter (but only for Writer)...
but in my case, I have the following requirements from my wiki users:

  • 1st: the conversion must handle merged cells vertically, horizontally and both.
  • 2nd: the conversion must remove all "cell formatting" like cell colors and sizes because the result is copy/paste in a wiki where tables "look-and-feel" must be homogeneous.
  • 3rd: the conversion must preserve all references, templates and links in cells.
  • 4th: the conversion should be as simple as possible.


Installation and usageEdit

  • STEP 1 : Create your table spreadsheet with Microsoft Excel, LibreOffice or Apache OpenOffice and save it in the OpenDocument format (.ods extension). Do not hesitate to create a "complex" table, to merge rows or columns, to add links, images, templates and references... Save and close your spreadsheet.
  • STEP 2 : Save the Ods2wiki.tar gawk script (see below to get the full script) in your spreadsheet directory

Linux Ubuntu usersEdit

  • STEP 3 : enter the following commands:
unzip *.ods
tar xf Ods2wiki.tar
gawk -f ods2wiki.gawk content.xml
  • STEP 4 : Then copy/paste the result into your wiki.

Microsoft Windows usersEdit

For Microsoft Windows users, you can download GNU Win32 gawk for Windows with this direct link. Then update your PATH env variable (probably with "C:\Program Files (x86)\GnuWin32\bin"). You may prefer to enter the following command before invoking the gawk script:

path C:\Program Files (x86)\GnuWin32\bin;%PATH%
  • STEP 3 : Unzip your spreadsheet, then enter:
gawk -f ods2wiki.gawk your_spreadsheet_filename\content.xml
  • STEP 4 : Then copy/paste the result into your wiki.

TestsEdit

Tested:

  • Mediawiki 1.17
  • Several Excel and LibreOffice ods spreadsheets with complex merged cells.

Not tested:

  • Very very complex spreadsheets, I am pretty sure it is easy to find spreadsheets that are not properly handled : (

Example of a complex tableEdit

This complex table has been created with LibreOffice and converted with ods2wiki.gawk script. Some template are missing in this wiki (but they are there on my wikis), the Spreadsheet_Example.ods can not be uploaded on this wiki too (ods format not supported).

Example of a complex table (click to download the media:Spreadsheet_Example.ods)
Title 1 Title 2 (horizontal merge) Title 3 Title 4
Tree Apple (link) Pear (link with image) Apricot Cherry
Ground Strawberry (template) Template:Y Pineberry (template with image) Strasberry Wild strawberry
separator (horizontal merge)
Shrub Berries (vertical merge) Raspberry [1] (reference) Currant (horizontal & vertical merge)
Mid (complex merge) Blackberry [2] (reference)
Blackcurrant (horizontal merge)

ods2wiki.gawk scriptEdit

Save the following script as Ods2wiki.tar as the gawk extension is most of the time not supported and risky.

# 
# ods2wiki.gawk : OpenDocument spreadsheet to Wikimedia syntax converter
#
# Test: All colspan & rowspan combinations have been tested (non-empty cells)
#
# TODO Test all combinations of empty cells
#

BEGIN {
	# We parse an xml file so we are looking for "<something>..."
	RS="<"; FS=">";

	# wiki table start marker and caption
	print "{|"
	print "|+ The Caption"
  
	# separator between cells (start with headers so firstrow = 1)
	sep = "!"; 
	firstrow = 1; 
	nb_col = 0; # Table number of columns

	# Set to 1 for debugging
	debug = 0;
}

# Debug function
function logd(msg) {
	if (debug)
		print "DEBUG " msg;
}

# Looks for the end of the table
# </table:table>
/table:table>$/ {
	logd("/table " $0);
	# End of the table, no need to parse the remaining data
	# Go out with success
	exit;
}

# Looks for rows
# <table:table-row ...> or </table:table-row ...>
/table:table-row/ {
	logd("row " $0);
	if (index($0, "/table") != 0) {
		# The row has been parsed, now print it
					
		# Print the row if there is no need to repeat it
		if (repeated_rows == 0) {
			print "|-";
			print s;
		} else {
			# Print several time the row (most of the time empty rows)
			for (i = 0; i < repeated_rows; i++) {
				print "|-";
				print s;
			}
		}

		# If it was the 1st row (headers), change the separator for next rows
		if (firstrow) {
			firstrow = 0;
			sep = "|";
		}

	} else {
		# A new row is coming

		# Check if it is one or several empty rows
		MAX_ROW_REP = 300;
		if (split($0, a, "table:number-rows-repeated=\"") > 1) {
			logd("rows-repeated " a[1] ", " a[2]);
			split(a[2], b, "\"");
			repeated_rows = b[1];
			if (repeated_rows > MAX_ROW_REP)
				repeated_rows = -1; # Do nothing, avoiding an extra empty row
		} else
			repeated_rows = 0;

		# Prepare everything for the coming row
		s= "";
		firstcell = 1;
	}
}

# Looks into cells (for "spanned" and "empty cells" information)
# <table:table-cell ...>
/^table:table-cell/ {
	logd("cell " $0);

	# "spanned"
	colspan = "";
	rowspan = "";
	rowcolspan = "";

	if (split($0, a, "-spanned=\"") > 1) {
		logd("span " a[1] ", " a[2] ", " a[3]);
	
		split(a[2], b, "\""); 
		if (b[1] > 1)
			colspan = "colspan=\"" b[1] "\" ";

		split(a[3], c, "\"");
		if (c[1] > 1)
			rowspan = "rowspan=\"" c[1] "\" ";

		rowcolspan = colspan rowspan "| ";
	}

	# Looks for empty cells <table:table-cell .../>
	if (index($0, "/>") != 0) {
		logd("empty " $0);
		MAX_COL_REP = 50; # Filter big values like "16384" used for end-of-line
		MAX_XML_SIZE = 16384;

		# "repeated" or single empty cells
		if (split($0, a, "table:number-columns-repeated=\"") > 1) {
			logd("columns-repeated " a[1] ", " a[2]);
	
			split(a[2], b, "\"");
			if (b[1] < MAX_COL_REP)
				empty_cells = b[1];
			else {
				# Empty cells at the end-of-line so need complex computations
				if (nb_col == 0) {
					nb_col = MAX_XML_SIZE - b[1];
					logd("Table Size " nb_col);
					empty_cells = 0;
				} else {
					empty_cells = nb_col - (MAX_XML_SIZE - b[1]);
				}
			}
		
		} else {
			logd("single");
			empty_cells = 1;
		}

		# Update s with empty cells
		logd("empty_cells " empty_cells);
		# Handle first empty cell separator
		if (firstcell) {
			firstcell = 0;
			empty_cells--;
			s = sep rowcolspan;
		}
		for (i = 0; i < empty_cells; i++) 
			s = s " " rowcolspan sep sep;

	} # Looks for empty cells
}

# Looks for text (table data)
# <text:p ...>
/^text:p/ {
	logd("text " $0);
	sub(/text:p>/, "", $0);
	
	if (firstcell) {
		firstcell = 0;
		s = sep " " rowcolspan $0;
	} else {
		s = s " " sep sep " " rowcolspan $0;
	}
}

# Looks for consecutive spaces, converted to a single extra space
# because most of the time, the user does not want more spaces for its wiki
# table (else the user can use html "&nbsp;" code)
# <text:s/>
/^text:s/ {
	logd("text:s " $0);
	sub(/text:s(.)*\/>/, " ", $0);
	s = s $0;
}


END {
	# wiki table end marker
	print "|}"
}

Extension:CategoryTreeEdit

New feature: "CategoryTree in Sidebar stayed opened"Edit

Feature descriptionEdit

I have added the "stayed opened" feature to the Mediawiki CategoryTree extension. Now, when "my" users browse the wiki, the sidebar category tree stayed opened and it was the major "complaint" of my wiki users and they are very happy now :)

InstallationEdit

  • Mediawiki 1.22: Only the file ext.categoryTree.js has been modified so simply copy/paste the related source code (see the attached source code below).
  • Mediawiki 1.17 and CategoryTree-MW1.17-r85033: Only the file CategoryTree.js has been modified so simply copy/paste the related source code (see the attached source code below).

How does it work?Edit

  • A cookie is created when the user expands a node (the cookie name is the category name).
  • This cookie is deleted when the user collapses the node.
  • When the page is loaded, the cookie are read and a jquery call simulates a user click on the related categories.

TestsEdit

Tested:

  • Mediawiki 1.17 and CategoryTree-MW1.17-r85033.
  • Mediawiki 1.22 and related CategoryTree.
  • Category tree in the sidebar.
  • On several Mediawiki instances on the same server.
  • With category names containing spaces and special characters like "&".
  • Latest Chrome, latest Firefox, Internet Explorer 9

Not tested:

  • "in-page" category trees (but first tests look fine).
  • Old Internet Explorer versions, Internet Explorer 10

Extra technical commentsEdit

Cookie expiration:

  • By default in this js source code, cookies are session-based, means they are clean-up when ~the browser is closed. Uncomment and adjust "'expires': XX," if you want your cookie to be more "permanent"...

ext.categoryTree.js source code (Mediawiki 1.22)Edit

  1 /**
  2  * JavaScript for the CategoryTree extension.
  3  *
  4  * @file
  5  * @ingroup Extensions
  6  * @author Daniel Kinzler, brightbyte.de
  7  * @copyright © 2006 Daniel Kinzler
  8  * @licence GNU General Public Licence 2.0 or later
  9  */
 10 
 11 ( function ( $, mw ) {
 12 
 13 const ckey = 'ct-'; /* cookie string beginning */
 14 
 15 var categoryTree = {
 16 
 17 	/** 
 18 	 * Set the category tree cookie to "true" if opened, else delete it with
 19 	 * a negative expiration date.
 20 	 * Note: By default, the cookies are session-based, means they are 
 21 	 * clean-up when ~the browser is closed. Uncomment and adjust 
 22 	 * "'expires': XX," if you want your cookie to be more "permanent"... 
 23 	 * @param cat Category name
 24 	 * @param state true if expanded, false if collapsed
 25 	 */
 26 	setCookie: function (cat, state) {
 27 		/* Prepare the cookie path to discriminate mediawiki instances */
 28 		var cpath = window.location.pathname;
 29 		cpath = cpath.substring( 0, cpath.lastIndexOf("/") + 1 );
 30 
 31 		if (state)
 32 			$.cookie( ckey + cat, state, { /*'expires': 30,*/ 'path': cpath } ); 
 33 		else
 34 			$.cookie( ckey + cat, state, { 'expires': -1, 'path': cpath } ); 
 35 	},
 36 
 37 
 38 	/**
 39 	 * Sets display inline to tree toggle
 40 	 */
 41 	showToggles: function () {
 42 		$( 'span.CategoryTreeToggle' ).css( 'display', 'inline' );
 43 
 44 		var toggles = $( 'span.CategoryTreeToggle' );
 45 		for ( var i = 0; i < toggles.length; ++i ) {
 46 			/* Extract the category name from the html code 'data-ct-title'.
 47 			 * Note: Special characters need to be "unescaped". */
 48 			var s = $( toggles[i] ).attr('data-ct-title');
 49 			var cat = unescape( s.replace( "\\x", "%" ) );
 50 			/* Read the cookie state related to the category name */
 51 			var state = $.cookie( ckey + cat );
 52 			/* Get the title status ('expand' or 'collapse') */
 53 			var title = $( toggles[i] ).attr('title');
 54 			/* Simulate a click to open the category tree */
 55 			if ( ( state == 'true' ) && ( title == 'expand' ) )
 56 				$( toggles[i] ).click();
 57 		}
 58 	},
 59 
 60 	/**
 61 	 * Handles clicks on the expand buttons, and calls the appropriate function
 62 	 *
 63 	 * @context {Element} CategoryTreeToggle
 64 	 * @param e {jQuery.Event}
 65 	 */
 66 	handleNode: function ( e ) {
 67 		var $link = $( this );
 68 		if ( $link.data( 'ct-state' ) === 'collapsed' ) {
 69 			categoryTree.expandNode( $link );
 70 		} else {
 71 			categoryTree.collapseNode( $link );
 72 		}
 73 	},
 74 
 75 	/**
 76 	 * Expands a given node (loading it's children if not loaded)
 77 	 *
 78 	 * @param {jQuery} $link
 79 	 */
 80 	expandNode: function ( $link ) {
 81 		// Show the children node
 82 		var $children = $link.parents( '.CategoryTreeItem' )
 83 				.siblings( '.CategoryTreeChildren' );
 84 		$children.show();
 85 
 86 		$link
 87 			.html( mw.msg( 'categorytree-collapse-bullet' ) )
 88 			.attr( 'title', mw.msg( 'categorytree-collapse' ) )
 89 			.data( 'ct-state', 'expanded' );
 90 
 91 		if ( !$link.data( 'ct-loaded' ) ) {
 92 			categoryTree.loadChildren( $link, $children );
 93 		}
 94 
 95 		categoryTree.setCookie( $link.data( 'ct-title' ), true );
 96 	},
 97 
 98 	/**
 99 	 * Collapses a node
100 	 *
101 	 * @param {jQuery} $link
102 	 */
103 	collapseNode: function ( $link ) {
104 		// Hide the children node
105 		$link.parents( '.CategoryTreeItem' )
106 			.siblings( '.CategoryTreeChildren' ).hide();
107 
108 		$link
109 			.html( mw.msg( 'categorytree-expand-bullet' ) )
110 			.attr( 'title', mw.msg( 'categorytree-expand' ) )
111 			.data( 'ct-state', 'collapsed' );
112 
113 		categoryTree.setCookie( $link.data( 'ct-title' ), false );
114 	},
115 
116 	/**
117 	 * Loads children for a node via an HTTP call
118 	 *
119 	 * @param {jQuery} $link
120 	 * @param {jQuery} $children
121 	 */
122 	loadChildren: function ( $link, $children ) {
123 		var $linkParentCTTag, ctTitle, ctMode, ctOptions;
124 
125 		/**
126 		 * Error callback
127 		 */
128 		function error() {
129 			var $retryLink;
130 
131 			$retryLink = $( '<a>' )
132 				.text( mw.msg( 'categorytree-retry' ) )
133 				.attr( 'href', '#' )
134 				.click( function ( e ) {
135 					e.preventDefault();
136 					categoryTree.loadChildren( $link, $children );
137 				} );
138 
139 			$children
140 				.text( mw.msg( 'categorytree-error' ) + ' ' )
141 				.append( $retryLink );
142 		}
143 
144 		$link.data( 'ct-loaded', true );
145 
146 		$children.html(
147 			$( '<i class="CategoryTreeNotice"></i>' )
148 				.text( mw.msg( 'categorytree-loading' ) )
149 		);
150 
151 		$linkParentCTTag = $link.parents( '.CategoryTreeTag' );
152 
153 		// Element may not have a .CategoryTreeTag parent, fallback to defauls
154 		// Probably a CategoryPage (@todo: based on what?)
155 		ctTitle = $link.data( 'ct-title' );
156 		ctMode = $linkParentCTTag.data( 'ct-mode' );
157 		ctMode = typeof ctMode === 'number' ? ctMode : undefined;
158 		ctOptions = $linkParentCTTag.data( 'ct-options' ) || mw.config.get( 'wgCategoryTreePageCategoryOptions' );
159 
160 		// Mode and options have defaults or fallbacks, title does not.
161 		// Don't make a request if there is no title.
162 		if ( typeof ctTitle !== 'string' ) {
163 			error();
164 			return;
165 		}
166 
167 		$.get(
168 			mw.util.wikiScript(), {
169 				action: 'ajax',
170 				rs: 'efCategoryTreeAjaxWrapper',
171 				rsargs: [ctTitle, ctOptions, 'json'] // becomes &rsargs[]=arg1&rsargs[]=arg2...
172 			}
173 		)
174 			.success( function ( data ) {
175 				data = data.replace(/^\s+|\s+$/, '');
176 				data = data.replace(/##LOAD##/g, mw.msg( 'categorytree-expand' ) );
177 
178 				if ( data === '' ) {
179 					switch ( ctMode ) {
180 						// CT_MODE_CATEGORIES = 0
181 						case 0:
182 							data = mw.msg( 'categorytree-no-subcategories' );
183 							break;
184 						// CT_MODE_PAGES = 10
185 						case 10:
186 							data = mw.msg( 'categorytree-no-pages' );
187 							break;
188 						// CT_MODE_PARENTS = 100
189 						case 100:
190 							data = mw.msg( 'categorytree-no-parent-categories' );
191 							break;
192 						// CT_MODE_ALL = 20
193 						default:
194 							data = mw.msg( 'categorytree-nothing-found' );
195 					}
196 
197 					data = $( '<i class="CategoryTreeNotice"></i>' ).text( data );
198 				}
199 
200 				$children
201 					.html( data )
202 					.find( '.CategoryTreeToggle' )
203 						.click( categoryTree.handleNode );
204 
205 				categoryTree.showToggles();
206 			} )
207 			.error( error );
208 	}
209 };
210 
211 // Register click events and show toggle buttons
212 $( function ( $ ) {
213 	$( '.CategoryTreeToggle' ).click( categoryTree.handleNode );
214 	categoryTree.showToggles();
215 } );
216 
217 }( jQuery, mediaWiki ) );

CategoryTree.js source code (Mediawiki 1.17)Edit

  1 /*
  2  * JavaScript functions for the CategoryTree extension, an AJAX based gadget
  3  * to display the category structure of a wiki
  4  *
  5  * @file
  6  * @ingroup Extensions
  7  * @author Daniel Kinzler, brightbyte.de
  8  * @copyright © 2006 Daniel Kinzler
  9  * @licence GNU General Public Licence 2.0 or later
 10  *
 11  * NOTE: if you change this, increment $wgCategoryTreeVersion
 12  *       in CategoryTree.php to avoid users getting stale copies from cache.
 13  */
 14 
 15 // Default messages if new code loaded with old cached page
 16 var categoryTreeErrorMsg = "Problem loading data.";
 17 var categoryTreeRetryMsg = "Please wait a moment and try again.";
 18 
 19 /* Prepare the cookie path to discriminate mediawiki instances */
 20 var cpath = window.location.pathname;
 21 cpath = cpath.substring(0, cpath.lastIndexOf("/") + 1);
 22 var ckey = 'ct-'; /* cookie string beginning */
 23 
 24 /* Set the category tree cookie to "true" if opened, else delete it with
 25  * a negative expiration date.
 26  * Note: By default, the cookies are session-based, means they are 
 27  * clean-up when ~the browser is closed. Uncomment and adjust 
 28  * "'expires': XX," if you want your cookie to be more "permanent"... */
 29 function setCookie(cat, state) {
 30 	if (state)
 31 		$.cookie( ckey + cat, state, { /*'expires': 30,*/ 'path': cpath } ); 
 32 	else
 33 		$.cookie( ckey + cat, state, { 'expires': -1, 'path': cpath } ); 
 34 }
 35 
 36 function categoryTreeNextDiv(e) {
 37 	var n= e.nextSibling;
 38 	while ( n && ( n.nodeType != 1 || n.nodeName != 'DIV') ) {
 39 		//alert('nodeType: ' + n.nodeType + '; nodeName: ' + n.nodeName);
 40 		n= n.nextSibling;
 41 	}
 42 
 43 	return n;
 44 }
 45 
 46 function categoryTreeExpandNode(cat, options, lnk) {
 47 	var div= categoryTreeNextDiv( lnk.parentNode.parentNode );
 48 
 49 	div.style.display= 'block';
 50 	lnk.innerHTML= categoryTreeCollapseBulletMsg;
 51 	lnk.title= categoryTreeCollapseMsg;
 52 	lnk.onclick= function() { categoryTreeCollapseNode(cat, options, lnk) }
 53 
 54 	if (!lnk.className.match(/(^| )CategoryTreeLoaded($| )/)) {
 55 		categoryTreeLoadNode(cat, options, lnk, div);
 56 	}
 57 
 58 	setCookie(cat, true);
 59 }
 60 
 61 function categoryTreeCollapseNode(cat, options, lnk) {
 62 	var div= categoryTreeNextDiv( lnk.parentNode.parentNode );
 63 
 64 	div.style.display= 'none';
 65 	lnk.innerHTML= categoryTreeExpandBulletMsg;
 66 	lnk.title= categoryTreeExpandMsg;
 67 	lnk.onclick= function() { categoryTreeExpandNode(cat, options, lnk) }
 68 
 69 	setCookie(cat, false);
 70 }
 71 
 72 function categoryTreeLoadNode(cat, options, lnk, div) {
 73 	div.style.display= 'block';
 74 	lnk.className= 'CategoryTreeLoaded';
 75 	lnk.innerHTML= categoryTreeCollapseBulletMsg;
 76 	lnk.title= categoryTreeCollapseMsg;
 77 	lnk.onclick= function() { categoryTreeCollapseNode(cat, options, lnk) }
 78 
 79 	categoryTreeLoadChildren(cat, options, div)
 80 }
 81 
 82 // FIXME Why can't this just use uneval()?
 83 function categoryTreeEncodeValue(value) {
 84 	switch (typeof value) {
 85 		case 'function':
 86 			throw new Error("categoryTreeEncodeValue encountered a function");
 87 			break;
 88 		case 'string':
 89 			s = '"' + value.replace(/([\\"'])/g, "\\$1") + '"';
 90 			break;
 91 		case 'number':
 92 		case 'boolean':
 93 		case 'null':
 94 			s = String(value);
 95 			break;
 96 		case 'object':
 97 			if ( !value ) {
 98 				s = 'null';
 99 			} else if (typeof value.length === 'number' && !(value.propertyIsEnumerable('length'))) {
100 				s = '';
101 				for (i = 0; i<value.length; i++) {
102 					v = value[i];
103 					if ( s!='' ) s += ', ';
104 					s += categoryTreeEncodeValue( v );
105 				}
106 				s = '[' + s + ']';
107 			} else {
108 				s = '';
109 				for (k in value) {
110 					v = value[k];
111 					if ( s!='' ) s += ', ';
112 					s += categoryTreeEncodeValue( k );
113 					s += ': ';
114 					s += categoryTreeEncodeValue( v );
115 				}
116 				s = '{' + s + '}';
117 			}
118 			break;
119 		default:
120 			throw new Error("categoryTreeEncodeValue encountered strange variable type " + (typeof value));
121 	}
122 
123 	return s;
124 }
125 
126 function categoryTreeLoadChildren(cat, options, div) {
127 	div.innerHTML= '<i class="CategoryTreeNotice">' + categoryTreeLoadingMsg + '</i>';
128 
129 	if ( typeof options == "string" ) { //hack for backward compatibility
130 		options = { mode : options };
131 	}
132 
133 	function f( request ) {
134 		if (request.status != 200) {
135 			div.innerHTML = '<i class="CategoryTreeNotice">' + categoryTreeErrorMsg + ' </i>';
136 			var retryLink = document.createElement('a');
137 			retryLink.innerHTML = categoryTreeRetryMsg;
138 			retryLink.onclick = function() {
139 				categoryTreeLoadChildren(cat, options, div, enc);
140 			}
141 			div.appendChild(retryLink);
142 			return;
143 		}
144 
145 		result= request.responseText;
146 		result= result.replace(/^\s+|\s+$/, '');
147 
148 		if ( result == '' ) {
149 			result= '<i class="CategoryTreeNotice">';
150 
151 			if ( options.mode == 0 ) {
152 				result= categoryTreeNoSubcategoriesMsg;
153 			} else if ( options.mode == 10 ) {
154 				result= categoryTreeNoPagesMsg;
155 			} else if ( options.mode == 100 ) {
156 					result= categoryTreeNoParentCategoriesMsg;
157 			} else {
158 				result= categoryTreeNothingFoundMsg;
159 			}
160 
161 			result+= '</i>';
162 		}
163 
164 		result = result.replace(/##LOAD##/g, categoryTreeExpandMsg);
165 		div.innerHTML= result;
166 
167 		categoryTreeShowToggles();
168 	}
169 
170 	var opt = categoryTreeEncodeValue(options);
171 	sajax_do_call( "efCategoryTreeAjaxWrapper", [cat, opt, 'json'] , f );
172 }
173 
174 function categoryTreeShowToggles() {
175 	var toggles = getElementsByClassName( document, 'span', 'CategoryTreeToggle' );
176 
177 	for( var i = 0; i<toggles.length; ++i ) {
178 		toggles[i].style.display = 'inline';
179 
180 		/* Extract the category name from the html code onclick. It is the 4th 
181 		 * element after a "'" onclick split (see the html code for details).
182 		 * Note: Special characters need to be "unescaped". */
183 		var s1 = ' ' + toggles[i].onclick;
184 		var s2 = s1.split("'");
185 		var cat = unescape(s2[4-1].replace("\\x", "%"));
186 		/* Read the cookie state related to the category name */
187 		var state = $.cookie( ckey + cat );
188 		/* Simulate a click to open the category tree according to the cookie */
189 		if (state == 'true')
190 				$(toggles[i]).click();
191 
192 	}
193 }
194 
195 // Re-show the CategoryTreeToggles
196 addOnloadHook(categoryTreeShowToggles);

Tips: Modify the sort order in the Sidebar CategoryTreeEdit

If you want to sort in a specific order, add a "sort key" in the related category (ie. "[[category:MyCat|sort key]]", read more here). Articles will be sorted in increasing order of "sort key" only in the related category.

Tips: Change collapse/expand/empty images in the CategoryTreeEdit

MediaWiki:categorytree-collapse-bulletEdit

 1 <!--[if lte IE 7]>
 2 <span style="font-family:'Courier New', Courier, monospace;">[<b>-</b>]</span>
 3 <![endif]-->
 4 
 5 <!--[if gte IE 8]>
 6 <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAA9QTFRFeXl53d3dmpqasbG
 7 x////GU0iEgAAAAV0Uk5T/////wD7tg5TAAAAK0lEQVQI12NwgQIG0hhCDAwMTCJAhqMCA4MiWEoIJABiOCooQhULi5BqMgB2bh4svs8t+QAAAABJRU5ErkJggg==" width="16" height="16""/>
 8 <![endif]-->
 9 
10 <![if !IE]>
11 <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAA9QTFRFeXl53d3dmpqasbG
12 x////GU0iEgAAAAV0Uk5T/////wD7tg5TAAAAK0lEQVQI12NwgQIG0hhCDAwMTCJAhqMCA4MiWEoIJABiOCooQhULi5BqMgB2bh4svs8t+QAAAABJRU5ErkJggg==" width="16" height="16""/>
13 <![endif]>

MediaWiki:categorytree-expand-bulletEdit

 1 <!--[if lte IE 7]>
 2 <span style="font-family:'Courier New', Courier, monospace;">[<b>+</b>]</span>
 3 <![endif]-->
 4 
 5 <!--[if gte IE 8]>
 6 <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAxQTFRF3d3deXl5////nZ2
 7 dQA6SoAAAAAN0Uk5T//8A18oNQQAAADNJREFUeNpiYEIDDMQKMKALMDOgCTDCRWACcBG4AEwEIcDITEAFuhnotmC4g4EEzwEEGAADqgHmQSPJKgAAAABJRU5ErkJggg==" width="16" height="16"/>
 8 <![endif]-->
 9 
10 <![if !IE]>
11 <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAxQTFRF3d3deXl5////nZ2
12 dQA6SoAAAAAN0Uk5T//8A18oNQQAAADNJREFUeNpiYEIDDMQKMKALMDOgCTDCRWACcBG4AEwEIcDITEAFuhnotmC4g4EEzwEEGAADqgHmQSPJKgAAAABJRU5ErkJggg==" width="16" height="16"/>
13 <![endif]>

MediaWiki:categorytree-empty-bulletEdit

 1 <!--[if lte IE 7]>
 2 <span style="font-family:'Courier New', Courier, monospace;">[<b>×</b>]</span>
 3 <![endif]-->
 4 
 5 <!--[if gte IE 8]>
 6 <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAAAXNSR0IArs4c6QAAAAxQTFRFeXl53d3dmpqasbGxwe9qYQAAAAF0Uk5TAE
 7 Dm2GYAAAABYktHRACIBR1IAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3AQaDSEvTJrcJgAAA
 8 AxJREFUCNdjYKAuAAAAUAABIhPodQAAAABJRU5ErkJggg==" alt="[x]" width="16" height="16"/>
 9 <![endif]-->
10 
11 <![if !IE]>
12 <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAAAXNSR0IArs4c6QAAAAxQTFRFeXl53d3dmpqasbGxwe9qYQAAAAF0Uk5TAE
13 Dm2GYAAAABYktHRACIBR1IAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3AQaDSEvTJrcJgAAA
14 AxJREFUCNdjYKAuAAAAUAABIhPodQAAAABJRU5ErkJggg==" alt="[x]" width="16" height="16"/>
15 <![endif]>

Extension:ImageMapEdit

New feature: "highlight all areas"Edit

Based on the wonderful javascript code from User:קיפודנחש, I add the feature to highlight all areas in grey when the image is loaded so it helps users to know where are the inter-active areas without searching them by using the mouse everywhere on the image (of course, yellow-highlighted areas are still there) because some users prefer searching areas on the image instead of using titles below the image. IMPORTANT NOTE: This feature is only for my wiki users and be sure you need such feature before adding it to your wiki.

Please refer to the full discussion thread (show replies) or the discussion thread post.


MediaWiki:Imagemap-Highlight.jsEdit

The full script is there (Mediawiki 1.17):

  1 $(document).ready(function() {
  2 
  3     var
  4 //add this class to all elements created by the script. the reason is that we call the script again on
  5 //window resize, and use the class to remove all the "artefacts" we created in the previous run.
  6 		myClassName = 'imageMapHighlighterArtefacts',
  7 		liHighlightClass = 'liHighlighting',
  8 // "2d context" attributes used for highlighting.
  9 		areaHighLighting = {fillStyle: 'rgba(213,92,25,0.0)', strokeStyle: 'rgba(68,105,125,1.0)', lineJoin: 'round', lineWidth: 4,
 10 				shadowOffsetX: 0, shadowOffsetY: 0, shadowBlur: 0, shadowColor: 'white'},
 11 		areaHighLightingAll = {fillStyle: 'rgba(68,105,125,0.5)', strokeStyle: 'rgba(68,105,125,1.0)', lineJoin: 'round', lineWidth: 4,
 12 				shadowOffsetX: 0, shadowOffsetY: 0, shadowBlur: 0, shadowColor: 'white'},
 13 //every imagemap that wants highlighting, should reside in a div of this 'class':
 14 		hilightDivMarker = '.imageMapHighlighter',
 15 // specifically for wikis - redlinks tooltip adds this message
 16 		pageDoesntExistMessage = (mw && mw.config && mw.config.get('wgUserLanguage') == 'he')
 17 			? ' (??? ???? ????)'
 18 			: ' (page does not exist)';
 19 
 20 
 21 	function drawMarker(context, areas) { // this is where the magic is done.
 22 
 23 		function drawPoly(coords) {
 24 			context.moveTo(coords.shift(), coords.shift());
 25 			while (coords.length)
 26 				context.lineTo(coords.shift(), coords.shift());
 27 		}
 28 
 29 		for (var i in areas) {
 30 			var coords = areas[i].coords.split(',');
 31 			context.beginPath();
 32 			switch (areas[i].shape) {
 33 				case 'rect': drawPoly([coords[0], coords[1], coords[0], coords[3], coords[2], coords[3], coords[2], coords[1]]); break;
 34 				case 'circle': context.arc(coords[0],coords[1],coords[2],0,Math.PI*2);  break;//x,y,r,startAngle,endAngle
 35 				case 'poly': drawPoly(coords); break;
 36 			}
 37 			context.closePath();
 38 			context.stroke();
 39 			context.fill();
 40 		}
 41 	}
 42 
 43 	function mouseAction(e) {
 44 		var $this = $(this),
 45 			context = $this.data('context');
 46 		$.extend(context, areaHighLighting);
 47 		var activate = e.type == 'mouseenter';
 48 		$this.toggleClass(liHighlightClass, activate);
 49 		context.clearRect(0, 0, context.canvas.width, context.canvas.height);
 50 		if (activate) {
 51 			drawMarker(context, $this.data('areas'));
 52 			if ($.client.profile().name === 'msie') {	// ie9: dimwit needs to be told twice.
 53 				context.clearRect(0, 0, context.canvas.width, context.canvas.height);
 54 				drawMarker(context, $this.data('areas'));
 55 			}
 56 		}
 57 	}
 58 
 59 	function mouseActionAll(e) {
 60 		var $this = $(this),
 61 			context = $this.data('context'),
 62 			map = $this.data('map');
 63 		$.extend(context, areaHighLightingAll);
 64 		if (e.type == 'mouseenter') {
 65 			$('area', map).each(function() {
 66 				var $this = $(this), text = this.title, areas = new Array();
 67 				areas.push(this);
 68 				drawMarker(context, areas);
 69 			});
 70 		} else {
 71 			context.clearRect(0, 0, context.canvas.width, context.canvas.height);
 72 		}
 73 	}
 74 
 75 	// massage the area "href" and create a human legible string to be used as the tooltip of "li"
 76 	function pageOfHref(href, cssClass) {
 77 		var page = href.replace(document.location.protocol + wgServer + "/wiki/", '').replace(/.*\/\//, '').replace(/_/g, ' ');
 78 		page = page.replace(/#(.*)/, function(toReplace){return toReplace.replace(/\.([\dA-F]{2})/g, '%$1');});
 79 		page = decodeURIComponent(page); // used for "title" of legends - just like "normal" wiki links.
 80 		if (cssClass.indexOf('new') + 1)
 81 			page += pageDoesntExistMessage;
 82 		return page;
 83 	}
 84 
 85 	function init() {
 86 		appendCSS('li.' + myClassName + '{white-space:nowrap;}\n' + //css for li element
 87 					'li.' + liHighlightClass + '{background-color:yellow;}\n' + //css for highlighted li element.
 88 					'.rtl li.' + myClassName + '{float: right; margin-left: 3em;}\n' +
 89 					'.ltr li.' + myClassName + '{float: left; margin-right: 3em;}');
 90 		$(hilightDivMarker+ ' img').each(function() {
 91 			var img = $(this), map = img.siblings('map:first');
 92 			if (!('area', map).length)
 93 				return;	//not an imagemap. inside "each" anonymous function, 'return' means "continue".
 94 			var w = img.width(), h = img.height();
 95 			var dims = {position: 'absolute', width: w + 'px', height: h + 'px', border: 0, top:0, left:0};
 96 			var jcanvas = $('<canvas>', {'class': myClassName})
 97 				.css(dims)
 98 				.attr({width: w, height: h});
 99 			var bgimg = $('<img>', {'class': myClassName, src: img.attr('src')})
100 				.css(dims);//completely inert image. this is what we see.
101 			var context = $.extend(jcanvas[0].getContext("2d"), areaHighLighting);
102 // this is where the magic is done: prepare a sandwich of the inert bgimg at the bottom,
103 // the canvas above it, and the original, image, on top.
104 // so canvas won't steal the mouse events.
105 // pack them all TIGHTLY in a newly minted "relative" div, so when page chnage
106 // (other scripts adding elements, window resize etc.), canvas and imagese remain aligned.
107 			var div = $('<div>').css({position: 'relative', width: w + 'px', height: h + 'px'});
108 			img.before(div);	// put the div just above the image, and ...
109 			div.append(bgimg)	// place the background image in the div
110 				.append(jcanvas)// and the canvas. both are "absolute", so they don't occupy space in the div
111 				.append(img);	// now yank the original image from the window and place it on top.
112 			img.fadeTo(1, 0);	// make the image transparent - we see canvas and bgimg through it.
113 			var ol = $('<ol>', {'class': myClassName}).css({clear: 'both', marginTop: '1.5em', paddingLeft: '1.5em'});
114 			// ol below image, hr below ol. original caption pushed below hr.
115 			div.after($('<hr>', {'class': myClassName}).css('clear', 'both')).after(ol);
116 			var lis = {};	//collapse areas with same caption to one list item
117 			$('area', map).each(function() {
118 				var $this = $(this), text = this.title;
119 				var li = lis[text];	// title already met? use the same li
120 				if (!li) {			//no? create a new one.
121 					var href = this.href, cssClass = this['class'] || '';
122 					lis[text] = li = $('<li>', {'class': myClassName})
123 						.append($('<a>', {href: href, title: pageOfHref(href, cssClass), text: text, 'class': cssClass})) 
124 						.bind('mouseenter mouseleave', mouseAction)
125 						.data('areas', [])
126 						.data('context', context);
127 					ol.append(li);
128 				}
129 				li.data('areas').push(this);	//add the area to the li
130 				$(this).bind('mouseenter mouseleave', function(e) {li.trigger(e);})
131 			});
132 			$(this).bind('mouseenter mouseleave', mouseActionAll)
133 				.data('context', context)
134 				.data('map', map);
135 		});
136 	}
137 
138 	//has at least one "imagehighlight" div, and canvas-capable browser:
139 	if ($(hilightDivMarker).length && $('<canvas>')[0].getContext)
140 		init();
141 });

Tools for creating image mapEdit

  • GIMP: Using the menu filters/web/Image Map, you can create and save your image maps. It is preferred to save your image maps (coordinates and shapes) so then you can easily update them when you have a new revision of your image or diagram.
Converting html image maps to wiki image maps (do not forget to remove the comma between coordinates)
FROM html image map TO wiki image map
<map name="map">
<area shape="rect" coords="160,2,248,77" href="Step 1" />
<area shape="poly" coords="292,228,312,187,387,188" href="Step 2" />
<area shape="circle" coords="47,225,44" href="Step 3" />
</map>
rect 160 2 248 77 [[Step 1 | Step 1 (rectangle example)]]
poly 292 228 312 187 387 188 [[Step 2 | Step 2 (Polygon example)]]
circle 47 225 44 [[Step 3 | Step 3 (circle example)]]

To ease the conversion from html to wiki, copy the following lines in a file named html2wiki_map.sed:

# Keep only lines containing "area shape"
/area shape/!d
# Convert comma to space
s/,/ /g
# Get the final result
s/<area shape="\(.*\)" coords="\(.*\)" href="\(.*\)".*$/\1 \2 \3/

and then enter:

sed -f html2wiki_map.sed < my_html_gimp_image_map.map

GadgetsEdit

New feature: Copy2Clipboard SelectPreContentOnDoubleClickEdit

The following javascript source code allow the user to double-click on a "pre" content to select its content. This solution avoids flash and any complex "system based" solutions for clipboard copy because the user will the do "CTRL-C" :)


Note: It is more a "SelectPreContentOnDoubleClick" than a "Copy2Clipboard" feature because the user must enter CTRL+C, sorry for the misunderstanding...

MediaWiki:Copy2Clipboard.jsEdit

add the following line in MediaWiki:Common.js

importScript("MediaWiki:Copy2Clipboard.js");

And add the following source code in MediaWiki:Copy2Clipboard.js

 1 /* Source code from http://magp.ie/2010/04/07/auto-highlight-text-inside-pre-tags-using-jquery/ */
 2 var SelectPreContentOnDoubleClick = function ($) {
 3   $('#content pre').dblclick(function() {
 4     var refNode = $(this)[0];
 5     var selection = window.getSelection();
 6     var range = document.createRange();
 7     range.selectNodeContents(refNode);
 8     selection.removeAllRanges();
 9     selection.addRange(range);
10   });
11 }
12 
13 $(document).ready(SelectPreContentOnDoubleClick);