User:DarTar/wg system.js

/*! * jQuery contextMenu - Plugin for simple contextMenu handling * * Version: 1.6.6 * * Authors: Rodney Rehm, Addy Osmani (patches for FF) * Web: http://medialize.github.com/jQuery-contextMenu/ * * Licensed under *  MIT License http://www.opensource.org/licenses/mit-license *  GPL v3 http://opensource.org/licenses/GPL-3.0 * */ // ',		nowiki:              ']*>(.|\\n|\\r)*? ',		gallery:            '', close_table:        '\\|\\}| ', open_div:           ']*>', close_div:          ' ', table_row:          '\\|-', line_break:         '\\n', open_markup:        '<[^\\/][^>]*>', close_markup:       ']*>', bold:               "'''", italics:            "''", quote:              "'|\\\"",		ellipsis:            '\\.\\.\\.',		period:              '\\.',		comma:               ',',		exlamation:          '!',		question:            '\\?',		colon:               '\\:',		semicolon:           '\\;',		bar:                 '\\|',		other:               '.'	},	concatTokens: function(tokens, lower, upper){		lower = lower||0		upper = upper||tokens.length		str = ''		for(var i = lower; i < upper; i++){			str += tokens[i].c		}		return str	},	NOTE_TEMPLATE: 'User:EpochFail/Snote' })

/** Converts MediaWiki markup into tokens. For possible tokens and the regexp they match, see `WG.TOKEN_MAP`. An arbitrary map of tokens can be provided.


 * Parameters:

markup : String mediawiki markup to be tokenized tokens : Object a map from " " to " " (as a string) WG.Tokenizer = Class.extend({	init: function(markup, tokens){		this.tokens = tokens || WG.TOKEN_MAP		this.markup = markup		var expressionParts = []		for(type in this.tokens){			expressionParts.push(this.tokens[type])		}		this.tokenRE = RegExp(expressionParts.join("|"), "gi")		this.lookAhead = this.__nextToken	},

/**	Returns an array of all tokens found in `markup'. */	popAll: function{ var tokens = [] while(this.peek){ tokens.push(this.pop) }		return tokens },	/**	Returns the next token without removing it. */	peek: function{ if(this.lookAhead){ return this.lookAhead }else{ return null }	},	/**	Returns and removes the next token. */	pop: function{ if(this.lookAhead){ var temp = this.lookAhead var lastTime = new Date.getTime/1000 this.lookAhead = this.__nextToken WG.WAIT_TIME += (new Date.getTime/1000) - lastTime return temp }else{ return null }	},	__nextToken: function{ var timeBefore = new Date.getTime / 1000 var match = this.tokenRE.exec(this.markup) WG.WAIT_TIME += (new Date.getTime / 1000) - timeBefore if(!match){ return null }else{ var content = match[0] for(type in this.tokens){ var re = RegExp("^" + this.tokens[type] + "$", "gi") if(re.test(content)){ return {t:type, c:content} }			}			throw "Unexpected token content '" + content + "' matched no known token types." }	} })

/** Converts MediaWiki markup into chunks of content. Possible chunks to be returned include: - header - note - template - table - list item - div - paragraph break - line break - sentence

All chunks follow the scheme: {t:" ", id:, c:" "}

Joining the token content of chunks in order should reproduce the original MediaWiki markup.


 * Parameters:

markup : String mediawiki markup to be tokenized tokens : Object a map from " " to " " (as a string) */ WG.Chunker = function(markup, tokens){ this.tokenizer = new WG.Tokenizer(markup, tokens) this.lookAhead = this.__nextChunk }	/**	Returns an array of all chunks found in `markup'. */	WG.Chunker.prototype.popAll = function{ var chunks = [] while(this.peek){ chunks.push(this.pop) }		return chunks }	/**	Returns the next chunk without removing it. */	WG.Chunker.prototype.peek = function{ if(this.lookAhead){ return this.lookAhead }else{ return null }	}	/**	Returns and removes the next chunk */	WG.Chunker.prototype.pop = function{ if(this.lookAhead){ var temp = this.lookAhead this.lookAhead = this.__nextChunk return temp }else{ return null }	}	WG.Chunker.prototype.__nextChunk = function{ if(!this.chunkId){ this.chunkId  = 0 }		if(this.tokenizer.peek){ //We have tokens to process switch(this.tokenizer.peek.t){ case "open_template": return { t: "template", c: WG.concatTokens(this.__template), id: this.chunkId++ }				case "open_note": var tokens = this.__note return { t: "note", c: WG.concatTokens(tokens), id: this.chunkId++, val: WG.concatTokens(tokens, 1,tokens.length-1) }				case "open_table": return { t: "table", c: WG.concatTokens(this.__table), id: this.chunkId++ }				case "open_div": return { t: "div", c: WG.concatTokens(this.__div), id: this.chunkId++ }				case "open_image": return { t: "image", c: WG.concatTokens(this.__image), id: this.chunkId++ }				case "def_item": return { t: "definition", c: WG.concatTokens(this.__definition), id: this.chunkId++ }				case "list_item": return { t: "def_list_item", c: WG.concatTokens([this.tokenizer.pop]), id: this.chunkId++ }				case "header": return { t: "header", c: WG.concatTokens([this.tokenizer.pop]), id: this.chunkId++ }				case "paragraph_break": return { t: "paragraph_break", c: WG.concatTokens([this.tokenizer.pop]), id: this.chunkId++ }				case "line_break": return { t: "break", c: WG.concatTokens([this.tokenizer.pop]), id: this.chunkId++ }				case "ref": return { t: "ref", c: WG.concatTokens([this.tokenizer.pop]), id: this.chunkId++ }				/*case "math": return { t: "math", c: WG.concatTokens([this.tokenizer.pop]), id: this.chunkId++ }*/				case "pre": return { t: "pre", c: WG.concatTokens([this.tokenizer.pop]), id: this.chunkId++ }				case "gallery": return { t: "gallery", c: WG.concatTokens([this.tokenizer.pop]), id: this.chunkId++ }				case "comment": return { t: "comment", c: this.tokenizer.pop.c,						id: this.chunkId++ }				case "white_space": return { t: "white_space", c: this.tokenizer.pop.c,						id: this.chunkId++ }				case "colon": return { t: "colon", c: this.tokenizer.pop.c,						id: this.chunkId++ }				case "source": return { t: "source", c: this.tokenizer.pop.c,						id: this.chunkId++ }				case "close_template": case "close_table": case "close_div": case "close_markup": case "exclamation": case "question": case "period": case "comma": case "table_row": case "close_internal_link": case "white_space": case "colon": case "semicolon": case "open_markup": case "italics": case "bold": case "quote": case "ellipsis": case "open_internal_link": case "external_link": case "entity": case "number": case "word": case "nowiki": case "math": case "other": tokens = this.__sentence return { t: 'sentence', c: WG.concatTokens(tokens), id: this.chunkId++, tokens: tokens }					break; default: throw "Unexpected token type '" + this.tokenizer.peek.t + "' found while generating chunks." }		}else{ //No more tokens. No more chunks. return null }	}	WG.Chunker.prototype.__template = function{ var tokens = [this.tokenizer.pop] var templates = 1 while(this.tokenizer.peek && templates > 0){ switch(this.tokenizer.peek.t){ case "open_template": //going one deeper! templates++ break; case "close_template": //coming out of the templates templates-- break; }			tokens.push(this.tokenizer.pop) }		return tokens }	WG.Chunker.prototype.__div = function{ var tokens = [this.tokenizer.pop] var divs = 1 while(this.tokenizer.peek && divs > 0){ switch(this.tokenizer.peek.t){ case "open_div": //going one deeper! divs++ break; case "close_div": //coming out of the divs divs-- break; }			tokens.push(this.tokenizer.pop) }		return tokens }	WG.Chunker.prototype.__table = function{ var tokens = [this.tokenizer.pop] var tables = 1 while(this.tokenizer.peek && tables > 0){ switch(this.tokenizer.peek.t){ case "open_table": //going one deeper! tables++ break; case "close_table": //coming out of the tables tables-- break; }			tokens.push(this.tokenizer.pop) }		return tokens }	WG.Chunker.prototype.__definition = function{ var tokens = [] var done = false while(this.tokenizer.peek && !done){ switch(this.tokenizer.peek.t){ case "colon":          //--- tokens.push(this.tokenizer.pop) tokens.push.apply(tokens, this.__extraDefinitionMatter) done = true; break; case "open_template":  //Suddently, a template. Eat it and its children. tokens.push.apply(tokens, this.__template) break; case "open_internal_link": //Suddenly, a link. Eat it and its children. tokens.push.apply(tokens, this.__internalLink) break; case "open_image": //Suddenly, an image. Eat it and its children. tokens.push.apply(tokens, this.__image) break; case "line_break": case "definition_item": case "header": case "pre": case "gallery": case "source": case "open_note":      // case "open_table":     // case "open_div":       //End of paragraph case "paragraph_break": // done = true break; default:               //Everything else tokens.push(this.tokenizer.pop) }		}		return tokens }	WG.Chunker.prototype.__extraDefinitionMatter = function{ var tokens = [] var done = false while(this.tokenizer.peek && !done){ switch(this.tokenizer.peek.t){ case "open_template":  //Suddently, a template. Eat it and its children. tokens.push.apply(tokens, this.__template) break; case "open_internal_link": //Suddenly, a link. Eat it and its children. tokens.push.apply(tokens, this.__internalLink) break; case "open_image": //Suddenly, an image. Eat it and its children. tokens.push.apply(tokens, this.__image) break; case "line_break": case "def_item": case "header": case "pre": case "gallery": case "source": case "open_note":      // case "open_table":     // case "open_div":       //End of paragraph case "paragraph_break": // done = true break; default:               //Everything else tokens.push(this.tokenizer.pop) }		}		return tokens }	WG.Chunker.prototype.__sentence = function{ var tokens = [] var done = false while(this.tokenizer.peek && !done){ switch(this.tokenizer.peek.t){ case "exclamation":    //--- case "question":       //End of sentence case "period":         // case "colon":          // case "ellipsis":       //--- tokens.push(this.tokenizer.pop) tokens.push.apply(tokens, this.__extraSentenceMatter) done = true; break; case "open_template":  //Suddently, a template. Eat it and its children. tokens.push.apply(tokens, this.__template) break; case "open_internal_link": //Suddenly, a link. Eat it and its children. tokens.push.apply(tokens, this.__internalLink) break; case "open_image": //Suddenly, an image. Eat it and its children. tokens.push.apply(tokens, this.__image) break; case "def_item": case "header": case "pre": case "gallery": case "source": case "open_note":      // case "open_table":     // case "open_div":       //End of paragraph case "paragraph_break": // done = true break; default:               //Everything else tokens.push(this.tokenizer.pop) }		}		return tokens }	WG.Chunker.prototype.__extraSentenceMatter = function{ var tokens = [] var done = false while(this.tokenizer.peek && !done){ switch(this.tokenizer.peek.t){ case "open_template":  //Suddently, a template. Eat it and its children. tokens.push.apply(tokens, this.__template) break; case "open_internal_link": // case "open_note":         // case "open_image":        // New element case "def_item":     // case "list_item":     // case "header":            // case "source":            // case "gallery":           // case "pre":               // case "open_table":     // case "open_div":       //New paragraph case "paragraph_break": // case "open_markup":        // case "italics":            // case "bold":               // case "open_internal_link": // New sentence case "external_link":      // case "entity":             // case "word":               // case "other":              // done = true break; default:               //Everything else tokens.push(this.tokenizer.pop) }		}		return tokens }	WG.Chunker.prototype.__internalLink = function{ var tokens = [this.tokenizer.pop] var links = 1 while(this.tokenizer.peek && links > 0){ switch(this.tokenizer.peek.t){ case "open_internal_link": //going one deeper! links++ break; case "close_internal_link": //coming out of the links links-- break; }			tokens.push(this.tokenizer.pop) }		return tokens }	WG.Chunker.prototype.__image = function{ var tokens = [this.tokenizer.pop] var links = 1 while(this.tokenizer.peek && links > 0){ switch(this.tokenizer.peek.t){ case "open_internal_link": //going one deeper! links++ break; case "close_internal_link": //coming out of the links links-- break; }			tokens.push(this.tokenizer.pop) }		return tokens }	WG.Chunker.prototype.__note = function{ var tokens = [this.tokenizer.pop] var templates = 1 while(this.tokenizer.peek && templates > 0){ switch(this.tokenizer.peek.t){ case "open_template": //going one deeper! templates++ break; case "close_template": //coming out of the links templates-- break; }			tokens.push(this.tokenizer.pop) }		return tokens }

if(!window.WG){WG = {}}

/** A simple interface for interacting with a sentence in an article.


 * Parameters:

span : jQuery | DOM element the span containing the sentence to be edited

*/ WG.SentenceInteractor = Class.extend({	init: function(span){		this.span = $(span)		if(this.span.hasClass("editing")){			return		}		this.span.addClass("editing")		var id = parseInt(this.span.attr("id").split("_")[1])		this.chunk = WG.chunks.get(id)		this.currentHTML = this.span.html		this.markup = {			ltrim:  this.chunk.c.match(/^\s*/)[0],			rtrim:   this.chunk.c.match(/\s*$/)[0],			trimmed: $.trim(this.chunk.c)		}		this.div = $(' ')			.addClass("sentence_interactor")			.insertAfter(this.span)		this.pane = $(' ')			.addClass("pane")			.css("position", "absolute")			.appendTo(this.div)			.hide		this.menu   = new WG.SentenceMenu(this)		this.editor = new WG.SentenceEditor(this)		this.editor.text(this.markup.trimmed)		this.editor.summary( "Updating sentence starting with \"" + 			this.markup.trimmed.substring(0, Math.min(50, this.markup.trimmed.length)) + 			"...\"" )		this.show		this.resizer = function(interactor){return function(e){			interactor.resize		}}(this)		$(window).resize(this.resizer)		//this.captureClickEvent = function(interactor){return function(e){		//	if(e.button == 2 && e.ctrlKey){		//		e.stopPropagation		//		return false;		//	}		//}}(this)		//this.span.mousedown(this.captureClickEvent)	},	resize: function{		this.pane.css('width', $('#bodyContent .mw-content-ltr').innerWidth - 15)		this.div.css("height", this.pane.outerHeight)	},	preview: function(callback){		WG.api.pages.preview( WG.PAGE_TITLE, this.markup.ltrim + $.trim(this.editor.text) + this.markup.rtrim, function(interactor, callback){return function(html){ html = html .replace(/<\/?p>/gi, '') .replace(/\n .*?<\/strong>/g, '') interactor.span.html(html) if(callback){callback(html)} }}(this, callback), function(interactor, callback){return function(error){ WG.error(error) }}(this, callback) )	},	save: function(callback){		this.editor.disable		//Update chunk		this.chunk.c = this.markup.ltrim + 			this.editor.text + 			this.markup.rtrim		WG.chunks.save( this.menu.minor, this.editor.summary + WG.SUMMARY_SUFFIX, function(interactor, callback){return function(html){ interactor.editor.enable interactor.span.html(html) interactor.preview(					function(interactor, callback){return function(html){						interactor.currentHTML = html						interactor.span.html(html)						interactor.exit						if(callback){callback}					}}(interactor, callback)				) }}(this, callback), function(interactor, callback){return function(error){ WG.error(error) interactor.editor.enable }}(this, callback) )	},	show: function{		this.pane.css('left', $('#bodyContent .mw-content-ltr').position.left - 15)		this.pane.css('width', $('#bodyContent .mw-content-ltr').innerWidth - 15)		this.div.animate( {				height: this.pane.outerHeight },			{				duration: 200 }		)		this.pane.slideDown(200)	},	hide: function(callback){		this.pane.slideUp(200)		this.div.animate( {height: 0}, {				duration: 200, complete: function(interactor, callback){return function{ interactor.div.hide if(callback){callback} }}(this, callback) }		)	},	cancel: function{		this.span.html(this.currentHTML)		this.exit	},	exit: function{		this.hide( function(interactor){return function{ interactor.div.remove }}(this) )		this.span.removeClass("editing")		$(window).unbind(this.resizer)		//this.unbind('mousedown', this.captureClickEvent)	} })

WG.SentenceMenu = Class.extend({	init: function(interactor){		this.div = $(' ')			.addClass("menu")			.prependTo(interactor.pane)		this.cancel = $(' ')			.addClass("button")			.addClass("cancel")			.text("cancel")			.attr("title", "cancel editing")			.click( function(interactor){return function{ interactor.cancel }}(interactor) )			.appendTo(this.div)		this.minorCheck = {			div: $(' ')				.addClass("minor")				.appendTo(this.div),			label: $(' ')				.text("minor")				.attr('for', "minor_sentence_edit"),			checkbox: $(' ')				.attr('type', "checkbox")				.attr('id', "minor_sentence_edit")				.prop('checked', true)		}		this.minorCheck.div.append(this.minorCheck.label)		this.minorCheck.div.append(this.minorCheck.checkbox)		this.save = $(' ')			.addClass("button")			.addClass("save")			.addClass("primary")			.text("save")			.attr("title", "save your changes to the sentence")			.click( function(interactor){return function{ interactor.save }}(interactor) )			.appendTo(this.div)		this.preview = $(' ')			.addClass("button")			.addClass("preview")			.text("preview")			.attr("title", "preview your change to the sentence")			.click( function(interactor){return function{ interactor.preview }}(interactor) )			.appendTo(this.div)	},	minor: function{		return this.minorCheck.checkbox.is(":checked")	},	hide: function{		this.div.hide	},	show: function{		this.div.show	} })

WG.SentenceEditor = Class.extend({	init: function(interactor){		this.div = $(' ')			.addClass('editor')			.appendTo(interactor.pane)		this.textPane = {			textarea: $(" ")				.addClass("text")				.appendTo(this.div)		}		this.summaryPane = {			label: $(' ')				.text('Summary: ')				.attr('for', interactor.chunk.id + "_summary")				.appendTo(this.div),			textarea: $(" ")				.addClass("summary")				.appendTo(this.div)				.attr('id', interactor.chunk.id + "_summary")				.attr('rows', 1)		}	},	text: function(val){		if(val){			this.textPane.textarea				.attr("rows", Math.max(2, Math.ceil(val.length/80)))				.val(val)			return this		}else{			return this.textPane.textarea.val		}	},	summary: function(val){		if(val){			this.summaryPane.textarea.val(val)			return this		}else{			return this.summaryPane.textarea.val		}	},	disable: function{		this.textPane.textarea.prop('disabled', true)		this.summaryPane.textarea.prop('disabled', true) },	enable: function{ this.textPane.textarea.prop('disabled', false) this.summaryPane.textarea.prop('disabled', false) } }) if(!window.WG){WG = {}}

/** Centers a jQuery element(s) horizontally in relation to another element (usually a containing element).


 * Parameters:

of : DOM element | jQuery the element to center around


 * Returns:

this jQuery element jQuery.fn.center = function(of){ of = $(of || window) this.css("position", "absolute") this.css(		"left",		(of.position.left + of.outerWidth/2) - 		(this.outerWidth/2)	) return this }

/** Gets the absolute bottom of an element including padding, borders and margin.


 * Returns:

int pixels of position jQuery.fn.outerBottom = function{ return this.position.top + this.outerHeight(true) }

/** Positions an element beneath another with a specified offset.


 * Parameters:

of : DOM element | jQuery the element to place beneath offset : int the number of pixels to offset the placement (defualts to zero)


 * Returns:

this jQuery element jQuery.fn.beneath = function(of, offset){ offset = parseInt(offset || 0) of = $(of || $('body')) this.css("position", "absolute") this.css(		"top",		of.position.top + of.outerHeight(true) + offset	) return this }

WG.Gbutton = function(displayName){ var innerSpan = $(' ') .append($('')) .append(			$('')				.text(displayName)		) return $(' ') .attr("type", "button") .addClass("btn") .append($(' ').append(innerSpan))

}

WG.lpad = function(number, width, padding){ padding = padding || 0 width -= number.toString.length; if ( width > 0 ){ return new Array( width + (/\./.test( number ) ? 2 : 1) ).join( '0' ) + number; }	return number; }

WG.dumpObj = function(obj){ str = 'Object: ' for(thing in obj){ str += "\n\t" + String(thing) + ": " + String(obj[thing]) }	WG.lastDumpedObj = obj return str }

if(!window.WG){WG = {}}

$.extend(WG, {	NOTE_HANDLE_HEIGHT: 25,	NOTE_LINK_INIT:         "User:EpochFail/Note_link_init",	NOTE_REFERENCE_INIT:     "User:EpochFail/Note_reference_init",	NOTE_REFERENCE_TEMPLATE: "User:EpochFail/Note_reference" })

/** Represents a group of notes in a drawer. WG.NoteDrawerGroup = Class.extend({	/**	Constructs a new NoteDrawer note Group.	*/	init: function(offset, notes){		this.div = $(' ')			.css("position", "absolute")		this.notes = []		notes = notes || []		for(i in notes){note = notes[i]			this.add(note)		}	},	/**	Adds a note to the group.	*/	add: function(note){		//Add node in appropriate location in group		for(var i in this.notes){var n = this.notes[i]			if(n.offset > note.offset){				this.notes.splice(i, 0, note)				note.viewer.div.insertBefore(n.viewer.div)				this.reposition				return			}		}		//Otherwise, add to the end.		this.notes.push(note)		this.div.append(note.viewer.div)		this.reposition	},	/**	Removes a note from the group if it exists in the group. Otherwise does	nothing.	*/	remove: function(note){		for(i in this.notes){var n = this.notes[i]			if(n == note){				note.viewer.div.detach				this.notes.splice(i, 1)				this.reposition }		}	},	/**	The absolute position top of the drawer including margin, padding and border. */	top: function{ return this.div.position.top },	/**	The absolute position bottom of the drawer including margin, padding and border. */	bottom: function{ //return this.div.outerBottom //instead, return what the bottom *should* be		return this.top + (WG.NOTE_HANDLE_HEIGHT * this.notes.length) },	/**	Detaches notes and removed self from DOM. */	del: function{ this.div.children.detach this.div.remove },	/**	*/	reposition: function{ if(this.notes.length > 0){ this.div.css("top", this.notes[0].offset) }	} })

/** Positions and aligns notes on the right side of the screen in an intelligent way. WG.NoteDrawer = Class.extend({	/**	Constructs a new NoteDrawer	*/	init: function{		this.div = $('')			.appendTo('#bodyContent')			.css('position', 'absolute')			.css('top', 0)			.css( 'right', -15			)			.css('height', $('#bodyContent').height)		this.groups = []		$(window).resize( function(drawer){return function(e){ if(drawer.reloadTimer){ clearTimeout(						drawer.resizeTimer					) }				drawer.reloadTimer = setTimeout(					function(drawer){return function(e){						drawer.reload					}}(drawer)				) }}(this) )	},	/**	Redraws the note drawer and re-arranges the notes when necessary. For 	example, when the window is resized.	*/	reload: function{		this.clear		var oldGroups = this.groups		this.groups = []		for(var i in oldGroups){var group = oldGroups[i]			for(i in group.notes){var note = group.notes[i]				this.add(note)			}		}	},	/**	Adds a set of notes to the drawer.	*/	load: function(notes){		this.clear		this.groups = []		for(var i in notes){note = notes[i]			//make sure the note is hidden so we can align it correctly.			note.viewer.hide			//get the location that this note would want its handle positioned.			//If this is the first note or there is no overlap			if( this.groups.length == 0 || this.groups[this.groups.length-1].bottom < note.offset ){				//Easy case. We just create a new group at our desired offset				var group = new WG.NoteDrawerGroup(note.offset)				group.add(note)				//Add it to the drawer				this.div.append(group.div)				//Add it to our list of groups				this.groups.push(group)			}else{				//There is currently a group in the way of where				//we want to put this note's handle				//Let's just add it to the previous group.				this.groups[this.groups.length-1].add(note)			}		}		$('div.note_viewer').css('overflow', 'visible')	},	/**	Adds a note to the drawer in the most appropriate group	*/	add: function(note){		for(var i in this.groups){var group = this.groups[i]			if(group.bottom < note.offset){				//Not to the offset yet.			}else if(group.top <= note.offset + WG.NOTE_HANDLE_HEIGHT){				//We want to be inside of a group that already exists				group.add(note)				return			}else{				//We passed up the spot we want to put this note. //Drop it in it's desired spot. var group = new WG.NoteDrawerGroup(note.offset) group.add(note) this.div.append(group.div) //Insert in position. Don't put it *before* the //beginning. this.groups.splice(Math.max(0, i-1), 0, group) return }		}		//If you get here, that means we are adding a note that is below //all previous notes. We can just add it where we want it. var group = new WG.NoteDrawerGroup(note.offset) group.add(note) this.div.append(group.div) this.groups.push(group) },	/**	Removes a note from the drawer. */	remove: function(note){ note.viewer.div.detach for(var i in this.groups){var group = this.groups[i] //We don't have to check if the note is in the group //since this function does nothing if it isn't.			group.remove(note) }	},	/**	Clears all groups from the drawer. */	clear: function{ if(this.groups){ for(var i in this.groups){var group = this.groups[i] group.del }		}	} })

/** Represents a note in an article. WG.Note = Class.extend({	init: function(span){		this.span = $(span)		if(!this.span.attr('id')){			throw "Span missing id."		}		this.id = this.span.attr('id')		this.viewer = new WG.NoteViewer(this)		this.editor = new WG.NoteEditor(this)		this.viewer.div.append(this.editor.div)		this.hide		this.span			.click( function(note){return function(e){ note.toggle }}(this) )			.hover( function(note){return function(e){ note.viewer.handle.div.addClass("hover") }}(this), function(note){return function(e){ note.viewer.handle.div.removeClass("hover") }}(this) )	},	chunkId: function{		return this.chunk.id	},	pageTitle: function{		return WG.TALK_PAGE_TITLE + "/" + this.id	},	offset: function{		return this.span.position.top + this.span.outerHeight(true)+3//fudge	},	cancel: function{		this.editor.compress	},	/**	Toggles between showing and hiding the note viewer/editor	*/	toggle: function{		if(this.hidden){			this.show		}else{			this.hide		}	},	/**	Animated hide operation	*/	hide: function(callback){		this.hidden = true		this.editor.hide		//this.viewer.div.css("overflow", "hidden")		this.viewer.hide(callback)	},	/**	Animated show operation.	*/	show: function(callback){		this.hidden = false		this.viewer.show( function(note, callback){return function{ note.editor.show if(callback){callback} }}(this, callback) )	},	/**	Loads a preview of whatever if in the editor into the viewer using the API	*/	preview: function(callback){		WG.api.pages.preview( WG.TALK_PAGE_TITLE, this.editor.val, function(note, callback){return function(html){ note.viewer.view(html) if(callback){callback(html)} }}(this, callback), function(error){ WG.console.error(error) }		)	},	/**	Saves a new version of the note based on what is currently in the editor	*/	save: function(callback){		//First disable the editor. No clicking or typing while I'm saving!		this.editor.disable		//Start the call to save the current note.		WG.api.pages.save( this.pageTitle, this.token, this.preamble + this.editor.val, "Updating note", false, function(note, callback){return function{ note.preview(					function(note, callback){return function(html){						note.savedHTML = html						if(callback){callback}					}}(this, callback)				) //Now you can use the editor again note.editor.enable note.hide }}(this, callback), function(note, callback){return function(message){ WG.console.error(message) //Something bad happened, but you're welcome to try again. note.editor.enable }}(this, callback) )	},	/**	Removes a note placeholder from the chunks.	*/	remove: function(callback){		if(confirm("Are you sure you'd like to remove this note?")){			this.editor.disable			var summary = 'Removing note with "'			summary += this.editor.val.substring(0, 250-(summary.length+4)) + '..."'			WG.chunks.remove(this.chunk)			WG.chunks.save( false, summary, function(note, callback){return function{ note.span.remove //Remove self from drawer note.hide(						function(note){return function{							WG.noteDrawer.remove(note)						}}(note)					) }}(this, callback), function(note, callback){return function(error){ WG.console.error(error) this.editor.enable }}(this, callback) )		}	},	/**	Refresh with subpage content	**/	load: function{		WG.api.pages.get( this.pageTitle, function(note){return function(markup, page){ note.__loadMarkup(markup) note.preview note.editor.enable note.token = page.edittoken }}(this), function(note){return function(message){ WG.console.error("Could not load note markup " + note.id + ": " + message) }}(this) )	},	__loadMarkup: function(markup){		var noteHeaderRE = RegExp( "\n*" )		var match = markup.match(noteHeaderRE)		if(match){			//Found a note header template. Yay!			var parts = markup.split(match[0])			this.preamble = parts[0] + match[0],			this.editor.val(parts.slice(1).join(match[0]))		}else{			//No template :(. Try to process the first level two header. var level2RE = /(^|\n)+==[^\n]+?==/ match = markup.match(level2RE) if(match){ var parts = markup.split(match[0]) this.preamble = parts[0] + match[0], this.editor.val(parts.slice(1).join(match[0])) }else{ this.preamble = '",				this.editor.val(markup)			}		}	} })

WG.OldNote = WG.Note.extend({	init: function(chunk, span){		this._super(span)		//var id = parseInt(chunkSpan.attr('id').split("_")[1])		if(chunk.t != "note"){			throw "Non-note chunk(" + chunk.id + ") type '" + chunk.t + "' to be edited. No can do duder."		}		this.chunk = chunk		this.load	} })

WG.NewNote = WG.Note.extend({	init: function(previousId, noteClass, id){		//Creates a human-readable, searchable timestamp		var id = [				[					WG.lpad(d.getUTCFullYear, 4), 					WG.lpad(d.getUTCMonth+1), 					WG.lpad(d.getUTCDate, 2)				].join('-'),				[					WG.lpad(d.getUTCHours, 2), 					WG.lpad(d.getUTCMinutes, 2), 					WG.lpad(d.getUTCSeconds, 2)				].join(":")			].join('_')		var span = $(' ')			.addClass(noteClass)			.css("display", "inline-block;")			.attr("id", id)		this._super(span)		this.previousChunk = WG.chunks.get(previousId)		this.show(function(note){return function{note.editor.expand}}(this))	},	saved: function{		return this.chunk != undefined	},	chunkId: function{		if(this.saved) return this._super		return undefined	},	save: function(callback){		if(this.saved) return this._super(callback)		else{this.__createPage(callback)}	},	__createPage: function(callback){ //First disable the editor. No clicking or typing while I'm saving! this.editor.disable //Create the note page with new note content WG.api.pages.create(			this.pageTitle,			"== Re. Inline |note==\n" +			"\n" + 			this.editor.val,			"Creating note page for " + WG.PAGE_TITLE + "",			function(note, callback){return function(save){				//Created the note page. Now update the article with the placeholder				note.__insertIntoArticle(callback)				note.__appendToTalkPage			}}(this, callback),			function(note){return function(message){				WG.console.error("Could not create note subpage: " + message)				note.editor.enable			}}(this)		) },	__insertIntoArticle: function(callback){ //Update chunks this.chunk = { id: this.previousChunk.id+1, t: "note", c: "", val: this.id		} WG.chunks.insert(this.chunk) //Save a new version WG.chunks.save(			false,			"Inserting note placeholder for " + this.pageTitle + "",			function(note, callback){return function(save){				var summary = 'Inserting note with "'				summary += note.editor.val.substring(0, 250-(summary.length+4)) + '..."'				//Preview the new content				note.preview( function(note, callback){return function(html){ note.savedHTML = html if(callback){callback} }}(this, callback) )				//Re-enable the editor				note.editor.enable				note.hide			}}(this, callback),			function(note, callback){return function(message){				WG.console.error("Could not update article with note placeholder: " + message)				note.editor.enable			}}(this, callback)		) },	__appendToTalkPage: function{ WG.api.pages.append(			WG.TALK_PAGE_TITLE,			"\n\n",			"Adding section for new note",			function(save){},			function(message){				WG.console.error("Could not append note to talk page: " + message)			}		) },	cancel: function{ if(this.saved) return this._super //DESTROY EVERYTHING and forget we even started this.span.remove //Remove self from drawer this.hide(			function(note){return function{				WG.noteDrawer.remove(note)			}}(this)		) } })

/** An animated interface for viewing a note embedded in wiki markup. WG.NoteViewer = Class.extend({	init: function(note){		this.div = $('')		this.handle = {			div: $(' ')				.appendTo(this.div)				.click( function(note){return function(e){ note.toggle }}(note) )				.hover( function(note){return function(e){ note.span.addClass("hover") }}(note), function(note){return function(e){ note.span.removeClass("hover") }}(note) )		}		var paneDiv = $(' ')		this.pane = {			div: paneDiv				.hide				.appendTo(this.div),			view: $(' ')				.appendTo(paneDiv)		}		this.hidden = true	},	/**	Loads new HTML into the note viewer.	*/	view: function(html){		this.pane.view.html(html)	},	/**	Animated hide operation	*/	hide: function(callback){		//this.viewer.div.css("overflow", "hidden")		this.pane.div.slideUp( 200,			function(viewer, callback){return function{ viewer.div.animate(					{						width: 0, 						right: 0					},					{						complete: function(callback){return function{							if(callback){callback}						}}(callback)					}				) }}(this, callback) )	},	/**	Animated show operation.	*/	show: function(callback){		this.div.animate( {width: 500, right: 500}, {				complete: function(viewer){return function{ viewer.pane.div.slideDown(200) if(callback){callback} }}(this) }		)	} })

/** An editor for creating and updating notes. WG.NoteEditor = Class.extend({	init: function(note){		this.note = note		this.div = $(' ')		this.remover = {			div: $(' ')				.text("remove")				.appendTo(this.div)				.click( function(editor){return function(e){ editor.note.remove }}(this) )		}		this.opener = {			div: $(' ')				.text("edit")				.addClass("primary")				.appendTo(this.div)				.click( function(editor){return function(e){ editor.expand }}(this) )		}		this.textarea = $(' ')			.hide			.appendTo(this.div)		this.canceller = {			div: $(' ')				.text("cancel")				.hide				.appendTo(this.div)				.click( function(editor){return function(e){ if(editor.div.hasClass("disabled")){return} editor.note.cancel }}(this) )		}		this.saver = {			div: $(' ')				.text("save")				.addClass("primary")				.hide				.appendTo(this.div)				.click( function(editor){return function(e){ if(editor.div.hasClass("disabled")){return} editor.note.save }}(this) )		}		this.previewer = {			div: $(' ')				.text("preview")				.hide				.appendTo(this.div)				.click( function(editor){return function(e){ if(editor.div.hasClass("disabled")){return} editor.note.preview }}(this) )		}		this.div.append($(' ').css('height', 0))	},	/**	Get and sets the value of the editor (what's in the text area)	*/	val: function(val){		if(val){			//Setting the value			this.textarea				.val(val)				.attr( "rows", Math.max(6, Math.floor(val.length/30)) )		}else{			//Asking for the value			return this.textarea.val		}	},	hide: function{		this.div.hide	},	show: function{		this.div.show	},	/**	Hides the edit pane with a nice little animation	*/	compress: function{		this.textarea.slideUp( 200,			function(editor){return function(e){ //Show the other buttons editor.saver.div.hide editor.previewer.div.hide editor.canceller.div.hide //Hide the edit div editor.opener.div.show editor.remover.div.show }}(this) )	},	/**	Shows the edit pane with a nice little animation	*/	expand: function{		this.div.show		//Hide the edit div		this.opener.div.hide		this.remover.div.hide		//Show the other buttons		this.saver.div.show		this.previewer.div.show		this.canceller.div.show		//Expand the text area		this.textarea.slideDown(200)		this.textarea.focus	},	/**	Cancels the editing operation. Reverts the markup in tghe textarea back	to the original and closes the editor.	*/	cancel: function{		//hide		this.compress		//restore old markup into textarea		this.textarea.val(this.note.chunk.val)		//Revert the viewer		this.note.viewer.revert	},	/**	Disables the buttons and text area in the editor.  This is useful when	new input should be restricted while an operation is being performed.	*/	disable: function{		//Disable text area		this.textarea.prop('disabled', true)		//add disabled class		this.div.addClass("disabled") },	/**	Enables (or re-enables) the buttons and text editor. */	enable: function{ //Re-enable textarea this.textarea.prop('disabled', false) //Remove the disabled class this.div.removeClass("disabled") },	/**	Focuses the cursor in the textarea */	focus: function{ this.textarea.focus } }) if(!window.WG){WG = {}}

WG.API = Class.extend({	init: function{		this.url = mw.config.get('wgServer') + mw.config.get('wgScriptPath') + "/api.php"		this.pages = new WG.Pages(this)	} })

WG.Pages = Class.extend({	init: function(api){		this.api = api	},	get: function(title, success, error){		success = success || function{}		error  = error   || function{}		WG.console.info("API: Requesting the current version of " + title + "...")		$.ajax({ url: this.api.url, dataType: "json", data: { action: 'query', prop:  'revisions|info', titles: title, rvprop: 'content|timestamp', intoken:'edit', format: 'json' },			type: "POST", context: this, success: function(success, error){return function(data, status){ //alert(WG.dumpObj(this)) if(status != "success"){ error("The API is unavilable: " + status) }else if(data.error){ error("Received an error from the API: " + data.error.code + " - " + data.error.info) }else if(!data.query || !data.query.pages){ error("Received an unexpected response from the API: " + WG.dumpObj(data)) }else { for(key in data.query.pages){ var page = data.query.pages[key] }					if(page.revisions){ var markup = page.revisions[0]['*'] WG.console.info("API: Received revision " + page.lastrevid + " of " + page.title + " with markup of length " + markup.length) }else{ var markup = undefined WG.console.info("API: Received info for missing page " + page.title) }					success(markup, page) }			}}(success, error), error: function(error){return function(jqXHR, status, message){ //Sometimes an error happens when the request is //interrupted by the user changing pages. if(status != 'error' || message != ''){ error("An error occurred while contacting Wikipedia's API: " + status + ": " + message) }			}}(error) })	},	append: function(title, markup, summary, success, error){		success = success || function{}		error  = error   || function{}		WG.console.info("API: Trying to append to " + title + "...")		this.get( title, function(api, title, markup, summary, success, error){return function(___, page){ WG.console.info("API: Appending markup of length " + markup.length + " to " + title + "...") $.ajax({					url: api.url,					dataType: "json",					data: {						action:    'edit',						title:      title,						appendtext: markup,						token:      page.edittoken,						summary:    summary,						format:     'json'					},					type: "POST",					success: function(summary, success, error){return function(data, status){						if(status != "success"){							error("The API is unavilable: " + status)						}else if(data.error){							error("Received an error from the API: " + data.error.code + " - " + data.error.info)						}else if(!data.edit || !data.edit.result){							error("Received an unexpected response from the API: " + WG.dumpObj(data))						}else if(data.edit.result != "Success"){							error("Saving the edit failed: " + WG.dumpObj(data.edit))						}else{							WG.console.info("API: Successfully appended text in revision " + data.edit.newrevid + " of " + data.edit.title + ": " + summary) success(data.edit) }					}}(summary, success, error), error: function(error){return function(jqXHR, status, message){ //Sometimes an error happens when the request is //interrupted by the user changing pages. if(status != 'error' || message != ''){ error("An error occurred while contacting Wikipedia's API: " + status + ": " + message) }					}}(error) })			}}(this.api, title, markup, summary, success, error),			function(error){return function(message){				error(message)			}}(error)		) },	save: function(title, token, touched, markup, summary, minor, success, error){ success = success || function{} error  = error   || function{} WG.console.info("API: Saving a new revision of " + title + " with mark of length " + markup.length + "...") $.ajax({			url: this.api.url,			dataType: "json",			data: {				action:         'edit',				title:           title,				text:            markup,				token:           token,				basetimestamp:   touched,				summary:         summary,				minor:           minor,				format:          'json'			},			type: "POST",			success: function(summary, success, error){return function(data, status){				if(status != "success"){					error("The API is unavilable: " + status)				}else if(data.error){					error("Received an error from the API: " + data.error.code + " - " + data.error.info)				}else if(!data.edit || !data.edit.result){					error("Received an unexpected response from the API: " + WG.dumpObj(data))				}else if(data.edit.result != "Success"){					error("Saving the edit failed: " + WG.dumpObj(data.edit))				}else{					WG.console.info("API: Successfully saved revision " + data.edit.newrevid + " of " + data.edit.title + ": " + summary) success(data.edit) }			}}(summary, success, error), error: function(error){return function(jqXHR, status, message){ //Sometimes an error happens when the request is //interrupted by the user changing pages. if(status != 'error' || message != ''){ error("An error occurred while contacting Wikipedia's API: " + status + ": " + message) }			}}(error) })	},	create: function(title, markup, summary, success, error){		success = success || function{}		error  = error   || function{}		WG.console.info("API: Trying to create " + title + "...")		this.get( title, function(api, title, markup, summary, success, error){return function(___, page){ if(page.missing == undefined){ throw "Failed to create page " + title + ". Already exists." }				api.save(title, page.edittoken, markup, summary, false, success, error) }}(this, title, markup, summary, success, error), function(error){return function(message){ error(message) }}(error) )	},	preview: function(title, markup, success, error){		success = success || function{}		error  = error   || function{}		WG.console.info("API: Sending markup of length " + markup.length + " for " + title + " to be parsed...")		$.ajax({ url: this.api.url, dataType: "json", data: { action: 'parse', title: title, text:  markup, prop:  'text', pst:   true, format: 'json' },			type: "POST", context: this, success: function(success, failure){return function(data, status){ if(status != "success"){ error("The API is unavilable: " + status) }else if(data.error){ error("Received an error from the API: " + data.error.code + " - " + data.error.info) }else if(!data.parse || !data.parse.text || !data.parse.text['*']){ error("Received an unexpected response from the API: " + WG.dumpObj(data)) }else{ var html = data.parse.text['*'] .replace(//gi, '') success(html) }			}}(success, error), error: function(error){return function(jqXHR, status, message){ //Sometimes an error happens when the request is //interrupted by the user changing pages. if(status != 'error' || message != ''){ error("An error occurred while contacting Wikipedia's API: " + status + ": " + message) }			}}(error) })	} }) if(!window.WG){WG = {}}

WG.Chunks = Class.extend({	init: function(chunks){		this.chunks = chunks	},	get: function(id, type){		id = parseInt(id)		if(!this.chunks[id]){			throw "Chunk id " + id + " was not found."		}else if(type && this.chunks[id].t != type){			throw "Chunk id " + id + " is of type '" + this.chunks[id].t + "', not '" + type + "'"		}else{			return this.chunks[id]		}	},	insert: function(chunk){		this.chunks.splice(chunk.id, 0, chunk)		//Update future chunks and representation		for(var i=this.chunks.length-1;i>=chunk.id+1;i--){			var upChunk = this.chunks[i]			var span = $('#chunk_' + upChunk.id)			span.attr('id', 'chunk_' + i)			upChunk.id = i		}	},	remove: function(chunk){		//Remove chunk from chunk list		this.chunks.splice(chunk.id, 1)		//Remove id from span		$('#chunk_' + chunk.id).removeAttr('id')		//Update the affected spans and chunks		for(var i=chunk.id;i<this.chunks.length;i++){			var upChunk = this.chunks[i] var span = $('#chunk_' + upChunk.id) span.attr('id', 'chunk_' + i)			upChunk.id = i		} },	toString: function{ var newMarkup = '' for(i in this.chunks){ newMarkup += this.chunks[i].c		} return newMarkup },	remarkup: function(sentenceClass, noteClass, headerClass){ markup = '' for(var i in this.chunks){ var chunk = this.chunks[i] if(chunk.t == "sentence" || chunk.t == "definition"){ markup += (					'' + 					chunk.c + ' '				) }else if(chunk.t == "note"){ markup += (					'' + 					chunk.c + ' '				) }/*else if(chunk.t == "header"){ markup += (					'' + 					chunk.c + '\n '				) }*/else{ markup += chunk.c			} }		return markup },	save: function(minor, summary, success, error){ WG.api.pages.save(			WG.PAGE_TITLE,			WG.token,			WG.touched,			this.toString,			summary,			minor,			success,			error		) } }) if(!window.WG){WG = {}} $.extend(WG, { SENTENCE_CLASS: "WG_sentence", NOTE_CLASS: "WG_snote", HEADER_CLASS: "WG_header", MARKUP_CLASS: "WG_markup", CONTEXT_MENU: $('') .append($(' ')			.addClass('edit')			.append($('') .attr('href', '#edit') .text('Edit sentence') )		)		.append($(' ')			.addClass('new_note')			.append($('') .attr('href', '#new_note') .text('Insert note') )		)		.appendTo($('body')), SUB_NOTE_CLASS: "note_container", WAIT_TIME: 0, PAGE_TITLE: wgPageName, TALK_PAGE_TITLE: wgFormattedNamespaces[wgNamespaceNumber+1] + ":" + wgTitle, api: new WG.API, SUMMARY_SUFFIX: "(WG)", SUMMARY_MAX_LENGTH: 255 }) $.extend(WG, { load: function{ WG.api.pages.get(			WG.PAGE_TITLE,			function(markup, page){				WG.token = page.edittoken				WG.touched = page.touched				WG.parseAndLoad(markup)			},			function(error){				WG.console.error(error)			}		) },	parseAndLoad: function(markup){ WG.console.info("Parsing article content...") WG.chunks = new WG.Chunks((new WG.Chunker(markup)).popAll) //WG.console.info("Sending new markup of length " + WG.remarkuped.length + " to the API.") WG.api.pages.preview(			WG.PAGE_TITLE,			WG.chunks.remarkup(WG.SENTENCE_CLASS, WG.NOTE_CLASS, WG.HEADER_CLASS),			function(html){				WG.html = html				$(document).ready( function(e){ $("#bodyContent .mw-content-ltr").html(WG.html) /*$.contextMenu(							{								menu: WG.CONTEXT_MENU,								selector: $("span." + WG.SENTENCE_CLASS),								callback: function(action, el, pos){									switch(action){										case "edit":											WG.loadSentenceInteractor(el)											break;										case "new_note":											WG.loadNoteCreater(el)											break;									}								}							}						)*/ $("span." + WG.SENTENCE_CLASS) .hover(								function(e){									if(e.ctrlKey){										$(e.currentTarget).addClass("hover")									}								},								function(e){									$(e.currentTarget).removeClass("hover")								}							) .click(								function(e){									WG.loadSentenceInteractor(e.currentTarget)								}							) /*$(							"div." + WG.HEADER_CLASS + " h2,"+							"div." + WG.HEADER_CLASS + " h3,"+							"div." + WG.HEADER_CLASS + " h4,"+							"div." + WG.HEADER_CLASS + " h5,"+							"div." + WG.HEADER_CLASS + " h6,"+							"div." + WG.HEADER_CLASS + " h7"							) .append(								$(" ")									.addClass(WG.MARKUP_CLASS)									.append("+ note")							)*/ var hash = window.location.hash.substring(1) WG.noteDrawer = new WG.NoteDrawer $.each(							$('span.' + WG.NOTE_CLASS),							function(i, chunkSpan){								var id = parseInt($(chunkSpan).attr('id').split("_")[1])								var span = $(chunkSpan).children($('span.' + WG.SUB_NOTE_CLASS))								var note = new WG.OldNote( WG.chunks.get(id, 'note'), span )								WG.noteDrawer.add(note)								if(note.id == hash){									note.show								}							}						) WG.afterLoad }				)			},			function(error){				WG.console.error(error)			}		) },	loadSentenceInteractor: function(e){ WG.lastSentenceInteractor = new WG.SentenceInteractor(e) },	loadNoteCreater: function(e){ var previousId = parseInt(e.attr('id').split("_")[1]) d = new Date var note = new WG.NewNote(previousId, WG.SUB_NOTE_CLASS) WG.lastNewNote = note note.span.insertAfter(e) WG.noteDrawer.add(note) },	console: new WG.HiddenConsole, error: function(message){ if(confirm(message + "\nWould you like to reload the page?")){ window.location.reload }	},	afterLoad: function{ if(window.setupPopups){ disablePopups setupPopups }	} })

WG.load //