Utilizador:BMNeuroMat/mooc.js

Fonte: Wikiversidade

Nota: Depois de publicar, poderá ter de contornar a cache do seu navegador para ver as alterações.

  • Firefox / Safari: Pressione Shift enquanto clica Recarregar, ou pressione Ctrl-F5 ou Ctrl-R (⌘-R no Mac)
  • Google Chrome: Pressione Ctrl-Shift-R (⌘-Shift-R no Mac)
  • Internet Explorer / Edge: Pressione Ctrl enquanto clica Recarregar, ou pressione Ctrl-F5
  • Opera: Pressione Ctrl-F5.
// <nowiki>

/*############################
## MEDIAWIKI API FUNCTIONS ###
############################*/

/**
 * Request a wiki page's plain wikitext content.
 * Uses 'action=raw' to get the page content.
 * @param {String} title of the wiki page
 * @param {int} section within the wiki page or 0 to retrieve whole page
 * @param {function} callback when the page content was retrieved successfully (page content will be passed as parameter)
 * @param {function} callback when the page content could not be retrieved (jqXHR object will be passed as parameter)
 */
function doPageContentRequest(pageTitle, section, sucCallback, errorCallback) {
	var url = "https://en.wikiversity.org/w/index.php?action=raw&title=" + pageTitle;
	if (section !== null) {
		url += "&section=" + section;
	}
	$.ajax({
		url: url,
		cache: false
	}).fail(function(jqXHR) {
		console.log('moocEditor.doPageContentRequest: page content request failed for page "' + pageTitle + ' section ' + section + '" (server status ' + jqXHR.status + ')');
		if (typeof errorCallback !== 'undefined') {
			errorCallback(jqXHR);
		}
	}).done(sucCallback);
}

/**
 * Retrieves edit tokens for any number of wiki pages.
 * @param {Array<String>} page titles of the wiki pages
 * @param {function} callback when the edit tokens were retrieved successfully
 */
function doEditTokenRequest(pageTitles, sucCallback) {
	var sPageTitles = pageTitles.join('|');
	
	// get edit tokens
	var tokenData = {
		'intoken': 'edit|watch'
	};
	$.ajax({
		type: "POST",
		url: "https://pt.wikiversity.org/w/api.php?action=query&prop=info&format=json&titles=" + sPageTitles,
		data: tokenData
	}).fail(function(jqXHR) {
		console.log('moocEditor.doEditTokenRequest: edit token request failed for pages "' + sPageTitles + '" (server status ' + jqXHR.status + ')');
	}).done(function(response) {
		var editTokens = parseEditTokens(response);
		if (editTokens.hasTokens()) {
			sucCallback(editTokens);
		} else {
			console.log('moocEditor.doEditTokenRequest: failed to get edit tokens for "' + sPageTitles + '" (server response: ' + JSON.stringify(response) + ')');
		}
	});
}

function doEditRequest(pageTitle, section, content, summary, sucCallback) {
	console.log('edit request: ' + pageTitle + ' section ' + section);
	doEditTokenRequest([ pageTitle ], function(editTokens) {
		var editToken = editTokens.get(pageTitle);
		
		var editData = {
			'title': pageTitle,
			'text': content,
			'summary': summary,
			'watchlist': 'watch',
			'token': editToken
		};
		if (section !== null) {
			editData.section = section;
		}
		
		$.ajax({
			type: "POST",
			url: "https://pt.wikiversity.org/w/api.php?action=edit&format=json",
			data: editData
		}).fail(function(jqXHR) {
			console.log('moocEditor.doEditRequest: edit request failed for page "' + pageTitle + '" (server status: ' + jqXHR.status + ')');
		}).done(function(response) {
			console.log('moocEditor.doEditRequest: server response: ' + JSON.stringify(response));
			//TODO handle errors
			sucCallback();
		});
	});
}

function addSectionToPage(pageTitle, sectionTitle, content, summary, sucCallback) {
	console.log('add section request: ' + pageTitle + ' section title ' + sectionTitle);
	doEditTokenRequest([ pageTitle ], function(editTokens) {
		var editToken = editTokens.get(pageTitle);
		
		var editData = {
			'title': pageTitle,
			'section': 'new',
			'sectiontitle': sectionTitle,
			'text': content,
			'summary': summary,
			'watchlist': 'watch',
			'token': editToken
		};
		
		$.ajax({
			type: "POST",
			url: "https://pt.wikiversity.org/w/api.php?action=edit&format=json",
			data: editData
		}).fail(function(jqXHR) {
			console.log('moocEditor.addSectionToPage: add section request failed for page "' + pageTitle + '" (server status: ' + jqXHR.status + ')');
		}).done(function(response) {
			console.log('moocEditor.addSectionToPage: server response: ' + JSON.stringify(response));
			//TODO handle errors
			sucCallback();
		});
	});
}

/**
 * Parses a server response containing one or multiple edit tokens.
 * @param {JSON} tokenResponse
 * @return {Object} edit tokens object - you can retrieve the edit token by passing the page title to the object's 'get'-function
 */
function parseEditTokens(tokenResponse) {
	var hasTokens = false;
	var editTokens = {
		'tokens': [],
		'add': function(title, edittoken) {
			var lTitle = title.toLowerCase();
			console.log('edittoken for "' + title + '": ' + edittoken);
			this.tokens[lTitle] = edittoken;
			hasTokens = true;
		},
		'get': function(title) {
			return this.tokens[title.toLowerCase()];
		},
		'hasTokens': function() {
			return hasTokens;
		}
	};
	var path = ['query', 'pages'];
	var crr = tokenResponse;
	for (var i = 0; i < path.length; ++i) {
		if (crr && crr.hasOwnProperty(path[i])) {
			crr = crr[path[i]];
		} else {
			console.log('moocEditor.parseEditTokens: missing object "' + path[i] + '"');
			crr = null;
			break;
		}
	}
	if (crr) {
		var pages = crr;
		for (var pageId in pages) {
			// page exists
	   		if (pages.hasOwnProperty(pageId)) {
	       		var page = pages[pageId];
	      		editTokens.add(page.title, page.edittoken);
			}
		}
	}
	return editTokens;
}

function getIndex(title, section, sucCallback) {
	doPageContentRequest(title, section, sucCallback);
}

function getScript(item, sucCallback, errorCallback) {
	doPageContentRequest(item.fullPath + '/script', 0, sucCallback, errorCallback);
}

function getQuiz(item, sucCallback, errorCallback) {
	doPageContentRequest(item.fullPath + '/quiz', 0, sucCallback, errorCallback);
}

function updateScript(item, scriptText, summary, sucCallback) {
	var editSummary = summary;
	if (editSummary === '') {
		editSummary = 'script update for MOOC ' + item.header.type + ' ' + item.fullPath;
	}
	doEditRequest(item.fullPath + '/script', 0, scriptText, editSummary, sucCallback);
}

function updateQuiz(item, quizText, summary, sucCallback) {
	var editSummary = summary;
	if (editSummary === '') {
		editSummary = 'quiz update for MOOC ' + item.header.type + ' ' + item.fullPath;
	}
	doEditRequest(item.fullPath + '/quiz', 0, quizText, editSummary, sucCallback);
}

function updateIndex(item, summaryAppendix, sucCallback) {
	var summary = item.header.type + ' ' + item.header.path + ': ' + summaryAppendix;
	if (item.header.path === null) {// changing root item
		summary = item.header.type + ':' + summaryAppendix;
	}
	doEditRequest(item.index.title, item.indexSection, item.tostring(), summary, sucCallback);
}

function createPage(pageTitle, content, summary, sucCallback) {
	doEditRequest(pageTitle, 0, content, summary, sucCallback);
}

function addChild(type, name, parent, summary, sucCallback) {
	// add item to parent
	var parentHeader = parent.header;
	var header = Header(parentHeader.level + 1, type, name, null);
	parent.childLines.push(header.tostring());
	console.log('new child for ' + parentHeader.type + ' ' + parentHeader.title + ': ' + header.tostring());
	
	// update MOOC index at parent position
	var itemIdentifier = type + ' ' + parentHeader.path + '/' + name;
	if (parentHeader.path === null) {// parent is root
		itemIdentifier = type + ' ' + name;
	}
	if (summary === '') {
		summary = itemIdentifier + ' added';
	}
	updateIndex(parent, summary, function() {
		// create item page
		doEditRequest(parent.fullPath + '/' + name, 0, parent.getInvokeCode(), 'invoke page for MOOC ' + itemIdentifier + ' created', sucCallback);
	});
}

function addLesson(name, item, summary, sucCallback) {
	addChild('lesson', name, item, summary, sucCallback);
}

function addUnit(name, item, summary, sucCallback) {
	addChild('unit', name, item, summary, sucCallback);
}

function createMooc(title, summary, sucCallback) {
	createPage('Categoria:' + title, '{{#invoke:Mooc|overview|base=' + title + '}}\n<noinclude>[[categoria:MOOC]]</noinclude>', summary, function() {// create category with overview
		createPage(title, '{{#invoke:Mooc|overview|base=' + title + '}}', summary, function() {// create MOOC overview page
			createPage(title + '/MoocIndex', '--MoocIndex for MOOC @ ' + title, summary, sucCallback);// create MOOC index
		});
	});
}

function addThread(item, talkPage, title, content, sucCallback) {
	item.setParameter(PARAMETER_KEY.NUM_THREADS, (item.discussion.threads.length + 1).toString());
	item.setParameter(PARAMETER_KEY.NUM_THREADS_OPEN, (item.discussion.getNumOpenThreads() + 1).toString());
	addSectionToPage(talkPage.title, title, content, 'q:' + title, function() {
		doEditRequest(item.index.title, item.indexSection, item.tostring(), 'new thread in item discussion', sucCallback);
	});
}

function saveThread(item, thread, sucCallback) {
	item.setParameter(PARAMETER_KEY.NUM_THREADS, item.discussion.threads.length.toString());
	item.setParameter(PARAMETER_KEY.NUM_THREADS_OPEN, item.discussion.getNumOpenThreads().toString());
	doEditRequest(thread.talkPage.title, thread.section, thread.tostring(), 'replied to "' + thread.title + '"', function() {
		doEditRequest(item.index.title, item.indexSection, item.tostring(), 'new reply in item discussion', sucCallback);
	});
}

function parseThreads(unparsedContent, sucCallback, errCallback) {
	console.log('parsing: ' + unparsedContent);
	var api = new mw.Api();
	var promise = api.post({
		'action': 'parse',
		'contentmodel': 'wikitext',
		'disablepp': true,
		'text': unparsedContent
	});
	promise.done(function(response) {
		console.log('moocEditor.parseThreads: server response: ' + JSON.stringify(response));
		var wikitext = response.parse.text['*'];
		sucCallback(wikitext);
	});
	if (typeof errCallback !== 'undefined') {
		promise.fail(errCallback);
	}
}

/**
 * Retrieves the URLs of any number of video files.
 * @param {Array<String>} array of titles of the files to retrieve an URL for (WARNING: should not include '_' to access the URL mapping in success callback correctly)
 * @param {function} callback when the URLs were retrieved successfully (An array mapping (page title) -> (url) will be passed. The page titles will not contain '_' but spaces.)
 */
function getVideoUrls(fileTitles, sucCallback) {
	//WTF imageinfo does also work on video files
	var sFileTitles = fileTitles.join('|');
	var api = new mw.Api();
	api.get({
		action: 'query',
		prop: 'videoinfo',
		titles: sFileTitles,
		viprop: 'url'
	}).done(function(data) {
		console.log(JSON.stringify(data));
		var path = ['query', 'pages'];
		var crr = data;
		for (var i = 0; i < path.length; ++i) {
			if (crr && crr.hasOwnProperty(path[i])) {
				crr = crr[path[i]];
			} else {
				console.log('moocEditor.getVideoUrl: missing object "' + path[i] + '"');
				crr = null;
				break;
			}
		}
		var fileUrls = [];
		if (crr) {
			var pages = crr;
			for (var pageId in pages) {
				// page exists
	   			if (pages.hasOwnProperty(pageId)) {
	       			var page = pages[pageId];
	      			fileUrls[page.title] = page.videoinfo[0].url;
	      			console.log(page.title + ' @ ' + page.videoinfo[0].url);
				}
			}
			sucCallback(fileUrls);
		}
	});
}

function hashChanged(hash) {
	if (hash.length > 0) {
		var section = $(hash);
		if (section.hasClass('collapsed')) {
			expand(section);
		}
	}
}

// ###############################
// ########## UTILITIES ##########
// ###############################

/**
 * Repeats a string value a given number of times.
 * @param {String} value to repeat
 * @param {int} number of times to repeat the value
 * @return {String} value repeated the given number of times.
 */
function strrep(value, numRepeat) {
	return new Array(numRepeat + 1).join(value);
}

/**
 * Splits a text into its single lines.
 * @param {String} multiline text
 * @return {Array} single text lines
 */
function splitLines(text) {
	return text.split(/\r?\n/);
}

/**
 * Calculates the header level of a wikitext line.
 * @param {String} wikitext line
 * @return {int} header level of the line passed, 0 if the line is no header
 */
function getLevel(line) {
	var sLevelStart = line.match('^=*');
	if (sLevelStart.length > 0 && sLevelStart[0]) {
		var sLevelEnd = line.match('=*$');
		if (sLevelEnd.length > 0 && sLevelEnd[0]) {
			return Math.min(sLevelStart[0].length, sLevelEnd[0].length);
		}
	}
	return 0;
}

// ###############################
// ######### UI UTILITIES ########
// ###############################

/**
 * Displays a notification message to the user.
 * Uses mw.Message to generate messages.
 * @param {String} message key
 * @param {Array} message parameters
 */
function notifyUser(msgKey, msgParams) {
	var msgValue = mw.msg(msgKey, msgParams);
	alert(msgValue);//TODO use notification API that seems to be disabled
}

//TODO rename to collapse/expandSection
/**
 * Collapses a section to a fix height making it expandable.
 * Only applies to non-collapsed sections that are larger than the collapsed UI would be.
 * @param {jQuery} section node to be collapsed
 */
function collapse(section) {// expandable via section header click
	var content = section.children('.content');
	if (section.hasClass('collapsed') || content.height() <= '80') {
		return;
	}
	section.addClass('collapsed');
	//TODO display layer labeled 'EXPAND'
	var btnReadMore = $('<div>', {
		'class': 'btn-expand'
	}).html('&#8595; ' + getMessageText('btn-expand-section') + ' &#8595;');
	btnReadMore.click(function() {
		expand(section);
		return false;
	});// expandable via button click
	section.append(btnReadMore);
	section.on('click', function() {
		expand(section);
		return true;
	});// expandable via section click (may target underlying elements)
	section.focusin(function() {
		expand(section);
		return true;
	});// expandable via focusing any child element (may target underlying elements)
	var btnHeight = btnReadMore.css('height');
	btnReadMore.css('height', '0');
	btnReadMore.stop().animate({
		'height': btnHeight
	}, function() {
		btnReadMore.css('height', null);
	});
	content.stop().animate({
		'height': '40px'
	}, 'slow');
}
/**
 * Expands a section to its full height making it collapsible again.
 * @param {jQuery} section node to be expanded
 */
function expand(section) {
	section.removeClass('collapsed');
	var content = section.children('.content');
	var crrHeight = content.css('height');
	var targetHeight = content.css('height', 'auto').height();
	section.children('.btn-expand').stop().animate({
		'height': '0'
	}, 'slow', function() {
		$(this).remove();
	});
	section.off('click');
	content.css('height', crrHeight);
	content.stop().animate({
		'height': targetHeight
	}, 'slow', function() {
		content.css('height', 'auto');
	});
}

//TODO remove if JS chained after CSS
function fixView(element, duration) {
	if (duration > 0) {
		console.log('fixing view at section ' + element.attr('id') + ' at pos ' + element.offset().top);
		element.css('background-color', '#FFF');
		var width = element.width();
		element.css('position', 'fixed');
		element.css('width', width);
		element.css('top', '0');
		var zIndex = element.css('z-index');
		element.css('z-index', 100);
		setTimeout(function() {
			element.css('position', 'relative');
			element.css('top', null);
			element.css('z-index', zIndex);
			console.log('section now at ' + element.offset().top);
			window.scroll(0, element.offset().top);// jump to section but fire jQuery scroll event
			$(window).scroll();
		}, duration);
	}
}
/**
 * Scrolls an element into the user's view.
 * The animation can handle movement of the element.
 * @param {jQuery object} element to scroll into view
 * @param {String} 'top'/'bottom' if the element should be aligned at the upper/lower screen border. Defaults to 'top'.
 * @param {int} duration of the scroll animation. Defaults to 1000ms.
 */
function scrollIntoView(element, align, duration) {
	if (typeof duration === 'undefined') {
		duration = 1000;
	}
	var Alignment = {
		'TOP': 1,
		'BOTTOM': 2
	};
	if (align === 'bottom') {
		align = Alignment.BOTTOM;
	} else {
		align = Alignment.TOP;
	}
	var targetTop;
	var adjustAnimation = function(now, fx) {
		var h = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
		var y = $(window).scrollTop();
		var crrTop = element.offset().top;
		if (align === Alignment.BOTTOM) {
			crrTop += element.height() - h;
			//TODO currently just if scrolled FAR enough
			if (crrTop + h - y < h) {// element already in view
				crrTop = y;
				fx.end = y;
				return true;
			}
		} else {
			//TODO check if already in view
		}
		if (nItemNav !== 'undefined' && nItemNav.hasClass('fixed')) {
			crrTop -= nItemNav.height();//TODO remove if workaround found
		}
		if (targetTop != crrTop) {
			targetTop = crrTop;
			fx.end = targetTop;//TODO is there a way to do this smoothly?
		}
		return false;
	};
	if (!adjustAnimation(0, {})) {
		$('html, body').stop().animate({
	        scrollTop: targetTop
	    }, {
	    	'duration': duration,
	    	'step': adjustAnimation
	    });
	}
}

/**
 * Reloads the current page.
 * @param {String} (optional) page anchor to be set
 */
function reloadPage(anchor) {
	if (typeof anchor === 'undefined') {
		document.location.search = document.location.search + '&action=purge';
	} else {
		window.location.href = document.URL.replace(/#.*$/, '') + '?action=purge' + anchor;
	}
}

// ###############################
// ######## INDEX LOADING ########
// ###############################

/**
 * Loads the header of a MOOC item from its index header line.
 * @param {String} item's header line from MOOC index
 * @param {String} MOOC base
 * @param {String} item path (absolute path including MOOC base)
 * @return {Object} MOOC item header loaded from header line. Returns null if header line malformed.
 */
function loadHeader(line, base, fullPath) {
	var level = getLevel(line);
	if (level > 0) {
		var iSeparator = line.indexOf('|');
		if (iSeparator > -1) {
			var type = line.substring(level, iSeparator);
			var title = line.substring(iSeparator + 1, line.length - level);
			var path = fullPath.substring(base.length + 1);// relative path
			console.log(type + ' "' + title + '" @ level ' + level + ' @ ' + path);
			return Header(level, type, title, path);
		}
	}
	console.log('malformed header: ' + line);
	return null;
}
/**
 * Loads a parameter of an item.
 * @param {Array} MOOC index lines
 * @param {int} start index of the parameter within index
 * @return {Object} item parameter extracted (key, value) and index of last line related to parameter (iEnd)
 */
function loadProperty(indexLines, iLine) {
	var line = indexLines[iLine];
	var iSeparator = line.indexOf('=');
	if (iSeparator != -1) {
		var paramLines = [];
		var key = line.substring(1, iSeparator);
		// read parameter value
		var i = iLine;
		var value = line.substring(iSeparator + 1);
		do {
			if (i > iLine) {// multiline value
				if (paramLines.length === 0 && value.length > 0) {// push first line value if any
					paramLines.push(value);
				}
				paramLines.push(line);
			}
			i += 1;
			line = indexLines[i];
		} while(i < indexLines.length && line.substring(0, 1) !== '*' && getLevel(line) === 0);
		i -= 1;
		
		if (paramLines.length > 0) {
			value = paramLines.join('\n');
		}
		return {
			'iEnd': i,
			'key': key,
			'value': value
		};
	} else {
		return null;
	}
}

/**
 * Creates a header instance holding identification data of the MOOC item.
 * @param {int} item level
 * @param {String} item type
 * @param {String} item title
 * @param {String} item path (relative to MOOC base)
 * @return {Object} MOOC item header to identify the item and write to MOOC index
 */
function Header(level, type, title, path) {
	return {
		'level': level,
		'path': path,
		'title': title,
		'type': type,
		'tostring': function() {
			var intendation = strrep('=', this.level);
			return intendation + this.type + '|' + this.title + intendation;
		}
	};
}
/**
 * Creates an item instance holding data extracted from MOOC index.
 * @param {Object} item header
 * @param {Object} MOOC index
 * @return {Object} MOOC item to get parameters and write to MOOC index
 */
function Item(header, index) {
	var loadingScript = false;
	var loadingQuiz = false;
	return {
		'childLines': [],
		'discussion': null,
		'fullPath': _fullPath,
		'header': header,
		'index': index,
		'indexSection': index.itemSection,
		'parameterKeys': [],
		'parameters': {},
		'script': null,
		'quiz': null,
		/**
		 * @return {String} invoke code used for the current item
		 */
		'getInvokeCode': function() {
			return '{{#invoke:Mooc|render|base=' + index.base + '}}';
		},
		/**
		 * Gets the value for an item parameter.
		 * @param {String} parameter key
		 * @return {?} Value stored for the parameter key passed. May be null.
		 */
		'getParameter': function(key) {
			return this.parameters[key];
		},
		/**
		 * Sets the value for an item parameter.
		 * @param {String} parameter key
		 * @param {?} parameter value
		 */
		'setParameter': function(key, value) {
			this.parameters[key] = value;
			if ($.inArray(key, this.parameterKeys) == -1) {
				this.parameterKeys.push(key);
			}
		},
		/**
		 * Retrieves the script resource for this item.
		 * @param {function} callback when the script was retrieved successfully (script gets passed as parameter)
		 * @param {function} (optional) callback when the script retrieval failed (jqXHR object gets passed as parameter)
		 */
		'retrieveScript': function(sucCallback, errCallback) {
			if (this.script !== null) {
				sucCallback(this.script);
			} else if (!loadingScript) {
				loadingScript = true;
				getScript(this, sucCallback, errCallback);
			} else {
				// does not happen
			}
		},
		/**
		 * Retrieves the quiz resource for this item.
		 * @param {function} callback when the quiz was retrieved successfully (quiz gets passed as parameter)
		 * @param {function} (optional) callback when the quiz retrieval failed (jqXHR object gets passed as parameter)
		 */
		'retrieveQuiz': function(sucCallback, errCallback) {
			if (this.quiz !== null) {
				sucCallback(this.quiz);
			} else if (!loadingQuiz) {
				loadingQuiz = true;
				getQuiz(this, sucCallback, errCallback);
			} else {
				// does not happen
			}
		},
		'tostring': function() {
			var lines = [];
			// header line
			if (this.indexSection !== null) {// except root item
				lines.push(this.header.tostring());
			}
			// parameters
			var key, value;
			this.parameterKeys.sort();
			for (var i = 0; i < this.parameterKeys.length; ++i) {
				key = this.parameterKeys[i];
				value = this.parameters[key];
				if (value.indexOf("\n") != -1) {// linebreak for multi line values
					lines.push('*' + key + '=\n' + value);
				} else {
					lines.push('*' + key + '=' + value);
				}
			}
			// children
			for (var c = 0; c < this.childLines.length; ++c) {
				lines.push(this.childLines[c]);
			}
			return lines.join('\n');
		}
	};
}
/**
 * Creates a MOOC index instance providing read access.
 * @param {String} MOOC page title
 * @param {String} MOOC base
 * @return {Object} MOOC index instance to retrieve item
 */
function MoocIndex(title, base) {
	var isLoading = false;
	return {
		'base': base,
		'item': null,
		'itemPath': null,
		'itemSection': null,
		'title': title,
		/**
		 * Sets the current item.
		 * @param {int} section of the item within the MOOC index
		 * @param {String} absolute path of the item
		 */
		'useItem': function(section, path) {
			this.itemSection = section;
			this.itemPath = path;
		},
		/**
		 * Retrieves the current item from the MOOC index.
		 * If the item is not cached this call will trigger a network request.
		 * @param {function} callback when the item was retrieved successfully (item gets passed as parameter)
		 * @param {function} (optional) callback when the item retrieval failed (jqXHR object gets passed as parameter)
		 */
		'retrieveItem': function(sucCallback, errCallback) {
			if (this.item !== null) {
				sucCallback(this.item);
			} else {
				var index = this;
				if (!isLoading) {// retrieve index and load item
					isLoading = true;
					getIndex(this.title, this.itemSection, function(indexContent) {
						var indexLines = splitLines(indexContent);
						var item;
						
						if (index.itemSection === null) {// root item
							item = Item(Header(0, 'mooc', index.base, null), index);
							// do not interprete index
							for (var i = 0; i < indexLines.length; ++i) {
								item.childLines.push(indexLines[i]);
							}
						} else {// index item
							var header = loadHeader(indexLines[0], index.base, index.itemPath);
							item = Item(header, index);
							// load properties and lines of child items
							var childLines = false;
							for (var i = 1; i < indexLines.length; i++) {
								if (!childLines) {
									if (getLevel(indexLines[i]) > 0) {
										childLines = true;
									} else {
										var property = loadProperty(indexLines, i);
										item.setParameter(property.key, property.value);
										i = property.iEnd;
									}
								}
								if (childLines) {
									item.childLines.push(indexLines[i]);
								}
							}
						}
						index.item = item;
						isLoading = false;
						sucCallback(item);
					});
				} else {// another process triggered network request
					setTimeout(function() {
						if (index.item !== null) {
							sucCallback(index.item);
						} else if (!isLoading && typeof(errCallback) !== 'undefined') {
							errCallback();
						}
					}, 100);
				}
			}
		}
	};
}

// ###############################
// ######## INITIALIZATION #######
// ###############################
function setMessage(key, value) {
	mw.messages.set(key, value);
}
function getMessageText(key, params) {
	return mw.message(key, params).text();
}
function loadMessages(languageKey) {
	setMessage('ask-question-button', 'Ask');
	setMessage('ask-question-title-label', 'Question title');
	setMessage('ask-question-text-label', 'Your question');
	setMessage('btn-ask-question-ui', 'Ask a question');
	setMessage('btn-expand-section', 'Read more');
	setMessage('btn-expand-thread', 'Read more');
	setMessage('btn-reply-ui', 'reply');
	setMessage('edit-default-summary-learningGoals', 'learning goals changed');
	setMessage('edit-default-summary-video', 'video changed');
	setMessage('edit-default-summary-script', '');
	setMessage('edit-default-summary-quiz', '');
	setMessage('edit-default-summary-furtherReading', 'further reading changed');
	setMessage('edit-default-text-quiz', '<quiz display=simple>\n' +
		'{Example question\n' +
		'|type="[]"}\n' +
		'correct answer\n' +
		'|| explanation for the correct answer is only displayed after the quiz is answered\n' +
		'- wrong answer\n' +
		'|| explanation for the wrong answer is only displayed after the quiz is answered\n' +
		'- another wrong answer\n' +
		'|| explanation for the wrong answer is only displayed after the quiz is answered\n' +
		'</quiz>');
	setMessage('edit-default-text-script', '<!-- put your $1 script here -->');
	setMessage('modal-button-edit', 'Save');
	setMessage('modal-button-add-lesson', 'Adicionar Lição');
	setMessage('modal-button-add-unit', 'Adicionar Unidade');
	setMessage('modal-button-create-mooc', 'Criar um MOOC');
	setMessage('modal-help-addLesson', 'Please notice that all underscores in a lesson title will be replaced with spaces.');
	setMessage('modal-help-addUnit', 'Please notice that all underscores in a unit title will be replaced with spaces.');
	setMessage('modal-help-createMooc', 'Please notice that all underscores in a MOOC title will be replaced with spaces.');
	setMessage('modal-help-furtherReading', 'Further reading items are separated by newlines and start with a "#".');
	setMessage('modal-help-learningGoals', 'Learning goals are separated by newlines and start with a "#".');
	setMessage('modal-help-quiz', 'Hint: Use the following link to edit the quiz at its wiki page: ' +
		'<a href="http://pt.wikiversity.org/w/index.php?title=$1/quiz&action=edit">edit quiz externally</a>' +
		'<br>Visit <a href="https://pt.wikiversity.org/wiki/Help:Quiz">Help:Quiz</a> for more information about quiz formats.');
	setMessage('modal-help-script', 'Hint: Use the following link to edit the script at its wiki page: ' +
		'<a href="http://pt.wikiversity.org/w/index.php?title=$1/script&action=edit">edit script externally</a>');
	setMessage('modal-help-video', 'The video can either be a text to be displayed or a video file such as "File:MyVideo.ogv" that will be displayed as thumbail. Keep in mind that this file must exist ether on commons.wikimedia.org or on en.wikiversity.org.');
	setMessage('modal-title-addLesson', 'Enter lesson name');
	setMessage('modal-title-addUnit', 'Enter unit name');
	setMessage('modal-title-createMooc', 'Enter MOOC title');
	setMessage('modal-title-furtherReading', 'Enter further reading');
	setMessage('modal-title-learningGoals', 'Enter learning goals');
	setMessage('modal-title-quiz', 'Enter quiz');
	setMessage('modal-title-script', 'Enter script');
	setMessage('modal-title-video', 'Enter video');
	setMessage('modal-summary-label', 'Enter edit summary');
	setMessage('reply-text-label', 'Your reply');
	setMessage('reply-button', 'Reply');
	setMessage('section-discussion', 'Discussion');
	setMessage('section-furtherReading', 'Further reading');
	setMessage('section-learningGoals', 'Learning goals');
	setMessage('section-quiz', 'Quiz');
	setMessage('section-script', 'Script');
	setMessage('section-units', 'Associated units');
	setMessage('section-video', 'Video');
}

// setup user agent for API requests
$.ajaxSetup({
	beforeSend: function(request) {
		request.setRequestHeader("User-Agent", "MOOC-JS/0.1 (https://en.wikiversity.org/wiki/User:Sebschlicht; sebschlicht@uni-koblenz.de)");
	}
});
var PARAMETER_KEY = {
	FURTHER_READING: 'furtherReading',
	LEARNING_GOALS: 'learningGoals',
	NUM_THREADS: 'numThreads',
	NUM_THREADS_OPEN: 'numThreadsOpen',
	VIDEO: 'video'
};
// extract item data from page DOM
var _base = $('#baseUrl').text();
var _fullPath = $('#path').text();
if (_fullPath === '') {// path of root item equals base
	_fullPath = _base;
}
var _indexSection = $('#section').text();
var _indexTitle = $('#indexUrl').text();
console.log('MOOC index @ ' + _indexTitle + ' for ' + _base);
loadMessages('en');
var _index = MoocIndex(_indexTitle, _base);
if (_indexSection !== '') {// use current item if not root
	_index.useItem(_indexSection, _fullPath);
}
var nItemNav;

// expand sections browsed to via anchors
if ("onhashchange" in window) {
	console.log("onhashchange");
	window.onhashchange = function() {
		hashChanged(window.location.hash);
	};
} else {
	console.log("setInterval");
	var prevHash = window.location.hash;
	window.setInterval(function() {
		if (window.location.hash != prevHash) {
			prevHash = window.location.hash;
			hashChanged(prevHash);
		}
	}, 100);
}

addStyleSheet('Utilizador:BMNeuroMat/mooc.css', function() {
	// make section navigation links scrolling smoothly
	nItemNav = $('#mooc-item-navigation');
	nItemNav.children('.section-link-wrapper').click(function() {
		var nSectionLink = $(this);
		var nSection = $('#' + nSectionLink.attr('id').substring(13));
		if (nSection.hasClass('collapsed')) {
			expand(nSection);
		}
		scrollIntoView(nSection);
		return false;
	});
	nItemNav.toggle(true);
	
	// collapse script section
	collapse($('#script'));
	// expand active section
	hashChanged(window.location.hash);
	//fix section for duration of section expansion animation
	//TODO find workarround
	if (window.location.hash !== '') {
		var section = $(window.location.hash);
		fixView(section, 600);
	}
	
	/**
	 * prepares headers
	 * * expand/collapse section when header clicked
	 * * fade in/out action buttons when entering section
	 */
	$('.section > .header').each(function() {
		var nHeader = $(this);
		var nSection = nHeader.parent();
		nHeader.click(function(e) {
			var target = $(e.target);
			if (!target.is('.header', ':header') && target.parents(':header').length === 0) {// filter clicks at action buttons
				return true;
			}
			if (nSection.hasClass('collapsed')) {
				expand(nSection);
			} else {
				collapse(nSection);
			}
			return false;
		});
		var nActions = nHeader.find('.actions');
		var nActionButtons = nActions.children().not('.edit-modal');
		nActionButtons.each(function() {// remove image link
			var btn = $(this);
			var img = btn.find('img');
			btn.append(img).find('a').remove();
		});
		nSection.mouseenter(function() {
			nActionButtons.stop().fadeIn();
		});
		nSection.mouseleave(function() {
			nActionButtons.stop().fadeOut();
		});
	});
	
	//TODO remove if not used by reply button
	// display overlay when mouse enters overlay parent
	$('.overlay').parent().mouseenter(function() {
		var overlay = $(this).children('.overlay');
		if (overlay.css('display') === 'none') {
			overlay.stop().toggle('fast');
		}
	});
	// hide overlay when mouse leaves overlay parent
	$('.overlay').parent().mouseleave(function() {
		var overlay = $(this).children('.overlay');
		if (overlay.css('display') !== 'none') {
			overlay.stop().toggle('fast');
		}
	});
	
	// prepare child units
	var unitButtons = [];
	var videoTitles = [];
	$('.children .unit').not('#addUnit').not('#addLesson').not('#addMooc').each(function() {
		var nChild = $(this);
		var nIconBar = nChild.find('.icon-bar');
		var nIconBarItems = nIconBar.find('li').not('.disabled');
		var iconBarOpacity = nIconBarItems.css('opacity');
		
		var nDownloadButton = nIconBar.find('li').eq(1);
		if (nDownloadButton.length > 0 && !nDownloadButton.hasClass('disabled')) {
			console.log('button active');
			unitButtons.push(nDownloadButton);
			videoTitles.push(nDownloadButton.children('a').attr('href').substring(6).replace(/_/g, ' '));
		}
		var nDisStatisticWrapper = nChild.find('.discussion-statistic-wrapper');
		var nDisStat = nDisStatisticWrapper.children('.discussion-statistic');
		var url = nChild.children('.content').children('.title').find('a').attr('href');
		nChild.mouseenter(function() {// show disussion stats when mouse enters child
			nDisStatisticWrapper.stop().fadeIn();
			nIconBarItems.css('opacity', '1');
		});
		nChild.mouseleave(function() {// hide discussion stats when mouse leaves child
			nDisStatisticWrapper.stop().fadeOut();
			nIconBarItems.css('opacity', iconBarOpacity);
		});
		nChild.click(function() {// item click (may target underlying elements)
			window.location = url;
			return true;
		});
		nDisStat.click(function() {// discussion statistic click
			window.location = url + '#discussion';
			return false;
		});
	});
	// retrieve video URLs
	getVideoUrls(videoTitles, function(videoUrls) {
		for (var i = 0; i < videoTitles.length; ++i) {
			var url = videoUrls[videoTitles[i]];
			if (url) {
				unitButtons[i].children('a').attr('href', url);
			}
		}
	});
	
	// make edit text links working in empty sections
	$('.empty-section .edit-text').click(function() {
		var section = $(this).parents('.section');
		if (section.length == 1) {
			section.children('.header').find('.edit-btn').click();
		}
	});
	
	// fix navigation bar staying scrollable
	var sidebar = $('#mooc-navigation');
	if (sidebar.length > 0) {
		var header = sidebar.find('.header-wrapper');
		var sidebarTop = sidebar.offset().top;
		var marginBottom = 10;
		function fixNavBarHeader(header) {
			header.css('width', header.outerWidth());
			header.css('position', 'fixed');
			header.addClass('fixed');
		}
		function resetNavBarHeader(header) {
			header.removeClass('fixed');
			header.css('position', 'absolute');
			header.css('width', '100%');
		}
		function fixNavBar(navBar) {
			navBar.removeClass('trailing');
			navBar.css('bottom', 'auto');
			navBar.css('position', 'fixed');
			navBar.css('top', 0);
			navBar.addClass('fixed');
		}
		function preventNavBarScrolling(navBar, marginBottom) {
			navBar.removeClass('fixed');
			navBar.css('top', 'auto');
			navBar.css('position', 'fixed');
			navBar.css('bottom', marginBottom);
			navBar.addClass('trailing');
		}
		function resetNavBar(navBar) {
			navBar.removeClass('fixed');
			navBar.removeClass('trailing');
			navBar.css('position', 'relative');
		}
		$(window).scroll(function() {
			var maxY = sidebarTop + sidebar.outerHeight();
			var h = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
			var y = $(this).scrollTop();
			
			var navBarScrolling = !sidebar.hasClass('trailing');
			var navBarFixed = sidebar.hasClass('fixed');
			var headerFixed = header.hasClass('fixed');
			if (y >= sidebarTop) {// navigation bar reached top screen border
				if (sidebar.outerHeight() <= h - marginBottom) {// fix navigation bar that fits in window
					if (!navBarFixed) {
						fixNavBar(sidebar);
					}
				} else {// navigation bar too large
					if (!headerFixed) { // fix navigation header
						fixNavBarHeader(header);
					}
					if (y + h >= maxY + marginBottom) {// disable scrolling when navigation bottom reached
						if (navBarScrolling) {
							preventNavBarScrolling(sidebar, marginBottom);
						}
					} else {// enable scrolling if still content available
						if (!navBarScrolling) {
							resetNavBar(sidebar);
						}
					}
				}
			} else {// navigation bar is back at its place
				if (headerFixed) {
					resetNavBarHeader(header);
				}
				if (navBarFixed) {
					resetNavBar(sidebar);
				}
			}
		});
	}
	
	// fix item navigation
	if (nItemNav.length > 0) {
		var itemNavTop = nItemNav.offset().top;//TODO ensure offset().top work correctly
		console.log('item nav is at ' + itemNavTop);
		$(window).scroll(function() {
			var y = $(window).scrollTop();
			var isFixed = nItemNav.hasClass('fixed');
			if (y >= itemNavTop) {
				if (!isFixed) {
					nItemNav.after($('<div>', {
						'id': 'qn-replace',
						'height': nItemNav.height()
					}));
					nItemNav.css('width', nItemNav.outerWidth());
					nItemNav.css('position', 'fixed');
					nItemNav.css('top', 0);
					nItemNav.addClass('fixed');
				}
			} else {
				if (isFixed) {
					nItemNav.css('width', '100%');
					nItemNav.css('position', 'relative');
					nItemNav.css('top', null);
					nItemNav.removeClass('fixed');
					nItemNav.next().remove();
				}
			}
		});
	}
	
	// fix header sections
	function setActiveSection(section) {
		var activeSection = $('.section').filter('.active');
		if (activeSection.length > 0) {
			setSectionActive(activeSection, false);
		}
		if (section != null) {
			setSectionActive(section, true);
		} else {
			//TODO replace with cross browser compatible solution (problems in e.g. Chrome 36.0.1985.125)
			//history.replaceState(null, null, window.location.pathname);
		}
	}
	function setSectionActive(section, isActive) {
		var sectionId = section.attr('id');
		var sectionAnchor = nItemNav.find('#section-link-' + sectionId);
		if (isActive) {
			sectionAnchor.addClass('active');
			section.addClass('active');
			//TODO replace with cross browser compatible solution (problems in e.g. Chrome 36.0.1985.125)
			//history.replaceState({}, '', '#' + sectionId);
		} else {
			sectionAnchor.removeClass('active');
			section.removeClass('active');
			resetHeader(section.children('.header'));
		}
	}
	function fixHeader(header, top) {
		header.css('position', 'fixed');
		header.css('top', top);
		header.css('width', header.parent().width());
		header.removeClass('trailing');
		header.addClass('fixed');
	}
	function resetHeader(header) {
		if (header.hasClass('fixed')) {
			header.css('position', 'absolute');
			header.css('width', '100%');
			header.removeClass('fixed');
		}
		header.css('top', 0);
		header.removeClass('trailing');
	}
	function trailHeader(header) {
		if (header.hasClass('fixed')) {
			header.css('position', 'absolute');
			header.css('width', '100%');
			header.removeClass('fixed');
		}
		header.css('top', header.parent().height() - header.outerHeight());
		header.addClass('trailing');
	}
	$(window).scroll(function() {
		var y = $(window).scrollTop();
		var h = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
		var marginTop = 0;
		if (nItemNav.hasClass('fixed')) {// correct scroll position
			marginTop = nItemNav.outerHeight() - 1;
			y += marginTop;
		}
		var activeSection = null;
		$('.section').each(function() {
			var section = $(this);
			var sectionHeader = section.children('.header');
			var sectionTop = section.offset().top;
			var sectionHeight = section.height();
			var isActive = section.hasClass('active');
			var isFixed = sectionHeader.hasClass('fixed');
			
			if (y >= sectionTop
				&& y <= sectionTop + sectionHeight) {// active section
				if (!isActive) {
					setActiveSection(section);
				}
				activeSection = section;
				if (y <= sectionTop + sectionHeight - sectionHeader.outerHeight()) {// header can be fixed
					if (!isFixed) {
						fixHeader(sectionHeader, marginTop);
					}
				} else {// header reached section bottom
					if (!sectionHeader.hasClass('trailing')) {
						trailHeader(sectionHeader);
					}
				}
			} else {
				if (isActive) {
					resetHeader(sectionHeader);
				}
			}
		});
		if (activeSection == null) {
			setActiveSection(null);
		}
	});
	    
	// inject modal boxes
	prepareModalBoxes();
	// fill modal boxes
	_index.retrieveItem(function(item) {
		fillModalBoxes(item);
	});
	// load discussion module
	addJavaScript('Utilizador:BMNeuroMat/moocDiscussions.js', function() {
		loadDiscussionUi();
	});
});

// ###################
// ###TODO:CLEAN UP###
// ###################

function saveChanges(idSection, value, summary) {
	var sucCallback = function() {
		reloadPage('#' + idSection);
	};
	if (idSection === 'script') {// update script resource
		if (_index.item.script === null) {
			// add category
			value += '\n<noinclude>[[categoria:' + _index.base + '-MOOC]]</noinclude>';
		}
		updateScript(_index.item, value, summary, sucCallback);
	} else if (idSection === 'quiz') {// update quiz resource
		if (_index.item.quiz === null) {
			// add category
			value += '\n<noinclude>[[categoria:' + _index.base + '-MOOC]][[categoria:Quizz]]</noinclude>';
		}
		updateQuiz(_index.item, value, summary, sucCallback);
	} else {// update index parameter
		var key = null;
		if (idSection === 'learningGoals') {
			key = PARAMETER_KEY.LEARNING_GOALS;
			value = value.replace(/(^|\n)\*/g, '\n#');
		} else if (idSection === 'video') {
			key =  PARAMETER_KEY.VIDEO;
			value = value.replace(/(^|\n)\*/g, '');
		} else if (idSection === 'furtherReading') {
			key = PARAMETER_KEY.FURTHER_READING;
			value = value.replace(/(^|\n)\*/g, '\n#');
		}
		if (key !== null) {
			if (summary === '') {
				summary = getMessageText('edit-default-summary-' + idSection);
			}
			_index.item.setParameter(key, value);
			updateIndex(_index.item, summary, sucCallback);
		}
	}
}

function prepareModalBoxes(idSectionCalled) {
	// fill modal boxes to save changes on item or its resources
	prepareModalBox('learningGoals', 'edit', 5, saveChanges);
	prepareModalBox('video', 'edit', 1, saveChanges);
	prepareModalBox('script', 'edit', 5, saveChanges);
	prepareModalBox('quiz', 'edit', 5, saveChanges);
	prepareModalBox('furtherReading', 'edit', 5, saveChanges);
	
	// fill modal boxes to add a MOOC item
	prepareModalBox('addLesson', 'add-lesson', 1, function(idSection, value, summary) {
		addLesson(value, _index.item, summary, function() {
			reloadPage();
		});
	});
	prepareModalBox('addUnit', 'add-unit', 1, function(idSection, value, summary) {
		addUnit(value, _index.item, summary, function() {
			reloadPage();
		});
	});
	prepareModalBox('createMooc', 'create-mooc', 1, function(idSection, value, summary) {
		createMooc(value, summary, function() {
			reloadPage();
		});
	}).find('.btn-save').prop('disabled', false);
	
	// make boxes closable via button
	$('.edit-modal').each(function() {
		var modal = $(this);
		modal.find('.btn-close').click(function() {
			closeModalBox(modal);
			return false;
		});
	});
	// make boxes closable via background click
	$('.edit-modal > .background').click(function(e) {
		closeModalBox($(e.target).parent());
		return false;
	});
	// make boxes closable via ESC key
    $('.edit-modal').bind('keydown', function(e) { 
        if (e.which == 27) {
        	closeModalBox($(this));
        }
    });
}
function closeModalBox(modal) {
	$('#mooc-item-navigation').css('z-index', 1001);
	modal.parent().parent().css('z-index', 1);
	modal.fadeOut();
}
function prepareModalBox(idSection, intentType, numLines, finishCallback) {
	// create modal box structure
	var modalBox = $('#modal-' + intentType + '-' + idSection);
	modalBox.append($('<div>', {
		'class': 'background'
	}));
	var boxContent = $('<div>', {
		'class': 'content'
	});
	boxContent.append($('<div>', {
		'class': 'btn-close'
	}));
	//TODO use real fieldset instead?
	var editFieldset = $('<div>', {
		'class': 'edit-field'
	});
	// label and textarea for value
	editFieldset.append($('<label>', {
		'for': 'edit-field-' + idSection,
		'class': 'label-title',
		'text': getMessageText('modal-title-' + idSection) + ':'
	}));
	var editField;
	if (numLines > 1) {
		editField = $('<textarea>', {
			'class': 'border-box',
			'id': 'edit-field-' + idSection
		});
	} else {
		editField = $('<input>', {
			'class': 'border-box',
			'id': 'edit-field-' + idSection,
			'type': 'text'
		});
	}
	editFieldset.append(editField);
	// label and input box for edit summary
	editFieldset.append($('<label>', {
		'for': 'summary-' + idSection,
		'class': 'label-summary',
		'text': getMessageText('modal-summary-label') + ':'
	}));
	var ibSummary = $('<input>', {
		'id': 'summary-' + idSection,
		'class': 'border-box summary',
		'type': 'text'
	});
	editFieldset.append(ibSummary);
	// help text
	var divHelpText = $('<div>', {
		'class': 'help',
	}).html(getMessageText('modal-help-' + idSection, _fullPath));
	editFieldset.append(divHelpText);
	boxContent.append(editFieldset);
	//TODO why not put in edit fieldset?
	// finish button
	var btnSave = $('<input>', {
		'class': 'btn-save',
		'disabled': true,
		'type': 'button',
		'value': getMessageText('modal-button-' + intentType)
	});
	boxContent.append(btnSave);
	btnSave.click(function() {
		if (!btnSave.prop('disabled')) {
			btnSave.prop('disabled', true);
			finishCallback(idSection, editField.val(), ibSummary.val());
		}
		return false;
	});
	modalBox.append(boxContent);
	return modalBox;
}
function fillModalBoxes(item) {
	// inject item data
	$('#edit-field-learningGoals').append(item.getParameter(PARAMETER_KEY.LEARNING_GOALS));
	$('#modal-edit-learningGoals').find('.btn-save').prop('disabled', false);
	$('#edit-field-video').val(item.getParameter(PARAMETER_KEY.VIDEO));
	$('#modal-edit-video').find('.btn-save').prop('disabled', false);
	$('#edit-field-furtherReading').append(item.getParameter(PARAMETER_KEY.FURTHER_READING));
	$('#modal-edit-furtherReading').find('.btn-save').prop('disabled', false);
	$('#modal-add-lesson-addLesson').find('.btn-save').prop('disabled', false);
	$('#modal-add-unit-addUnit').find('.btn-save').prop('disabled', false);
		
	// retrieve and inject additional resources
	var taScript = $('#edit-field-script');
	item.retrieveScript(function(scriptText) {
		taScript.text(scriptText).html();
		$('#modal-edit-script').find('.btn-save').prop('disabled', false);
	}, function(jqXHR) {
		if (jqXHR.status == 404) {// script missing
			taScript.text(getMessageText('edit-default-text-script', item.header.type)).html();
		}
		$('#modal-edit-script').find('.btn-save').prop('disabled', false);
	});
	var taQuiz = $('#edit-field-quiz');
	item.retrieveQuiz(function(quizText) {
		taQuiz.text(quizText).html();
		$('#modal-edit-quiz').find('.btn-save').prop('disabled', false);
	}, function(jqXHR) {
		if (jqXHR.status == 404) {// quiz missing
			taQuiz.text(getMessageText('edit-default-text-quiz')).html();
			$('#modal-edit-quiz').find('.btn-save').prop('disabled', false);
		}
	});
}

$(document).ready(function(){
	// make edit buttons clickable
	var showModalBox = function() {
		var btn = $(this);
		var modal = btn.next('.edit-modal');
		if (modal.length == 0) {
			modal = btn.next().next('.edit-modal');
		}
		//TODO what happens if no header but addLesson aso?
		var header = modal.parent().parent();
		$('#mooc-item-navigation').css('z-index', 1);
		header.css('z-index', 2);
		// show modal box with focus on edit field
		var editField = modal.find('.edit-field').children('textarea');
		modal.toggle('fast', function() {
			editField.focus();
		});
		return false;
	};
	$('.edit-btn').each(function() {
		var btn = $(this);
		btn.click(showModalBox);
	});
	// make add unit div clickable
	var divAddUnit = $('#addUnit');
	var imgAddUnit = divAddUnit.find('img');
	divAddUnit.find('span').append(imgAddUnit).children('a.image').remove();
	divAddUnit.click(showModalBox);
	divAddUnit.show();
	// make add lesson clickable
	var divAddLesson = $('#addLesson');
	var imgAddLesson = divAddLesson.find('img');
	divAddLesson.find('span').append(imgAddLesson).children('a.image').remove();
	divAddLesson.click(showModalBox);
	divAddLesson.show();
	// make add MOOC clickable
	var divAddMooc = $('#addMooc');
	var imgAddMooc = divAddMooc.find('img');
	divAddMooc.find('span').append(imgAddMooc).children('a').remove();
	divAddMooc.click(showModalBox);
	
	// let redlinks create invoke pages
	var invokeItem = Item(Header(0, null, null, null), _index);
	$('#mooc-navigation a.new').click(function() {
		var link = $(this);
		var itemUrl = link.attr('href').replace(/_/g, ' ');
		itemUrl = itemUrl.substring(0, itemUrl.length - 22);
		var itemTitle = itemUrl.substring(19);
		console.log(itemUrl + ": " + itemTitle);
		//TODO change to createInvokePage() and use mw.Message there
		createPage(itemTitle, invokeItem.getInvokeCode(), 'invoke page for MOOC item created', function() {
			window.location.href = itemUrl;
		});
		return false;
	});
});

//</nowiki>