User:RobLa/projectStatusHelper.js

/*   Project Status Helper rmoen@wikimedia.org psh.js v1.0

TODO: Conflict handling: Edit, Replace, Cleanup

//var testTxt = "Roan Kattouw and Trevor Parscal are rewriting the underlying data model (ve.dm) to achieve feature compatibility with the parser and correct a variety of problems that have been previously deferred. Inez Korczynski and Christian Williams have been continuing their work to stabilize and integrate the content editable layer (ve.ce) and have been working with Rob Moen, who has focused on getting the user interface elements working with the content editable layer (ve.ui). Gabriel Wicke has been working on improving the parser's ability to parse pages more quickly as well as increasing compatibility with existing features such as thumbnails. A big template-heavy page like Barack Obama can now be expanded in similar time as the production parser (80 seconds on a puny laptop) and 340MB of memory. It previously ran out of memory after consuming 1.6G and running for ~30 minutes."; var testTxt = ''; ( function (mw, $ ) {

/* Project status helper class */ Psh = function { var	_this = this, todayFormatted = (new Date).toJSON.substring(0, 10), $modalElements = $(' ')				.attr('id', 'projectStatusHelperForm') .append(					$(' ')						.attr('class', 'mw-ajax-loader')						.css({ position: 'absolute', width: '100%', height: '100%', top: '0px', left: '0px' })						.hide				).append(					$(' ').attr('id', 'projectStatusHelperInput')						.append( $(' ')								.append(									$(' ')										.attr('for', 'projectStatusName')										.text('Project Name:')								).append(									$(' ').css('clear', 'both')								).append(									$(' ')										.attr({ name: 'projectStatusName', id: 'projectStatusName' })										.bind('change', function { _this.updateStatusLink; }).append( $(' ')												.attr('value', '') .text('- Please Select Project -') )								)						).append( $(' ')								.append(									$(' ')										.text('Project Link: ')								).append(									$('')										.attr({ id: 'projectLink', target: '_blank' }).css('text-decoration', 'underline')								) ).append( $(' ')								.append(									$(' ')										.attr('for', 'projectStatusDate')										.text('Status Date:')								).append(									$(' ').css({'clear': 'both'})								).append(									$(' ')										.attr({ type: 'text', name: 'projectStatusDate', id: 'projectStatusDate' })										.val( todayFormatted )								).append(									$(' ').text( 'eg. YYYY-MM-DD' )								) ).append( $(' ')								.append(									$(' ')										.attr('for', 'projectStatusMonthlyFlag')										.text('Include in Monthly report?')								).append(									$(' ')										.attr({ type: 'checkbox', name: 'projectStatusMonthlyFlag', id: 'projectStatusMonthlyFlag' })								)						).append( $(' ')								.append(									$(' ')										.attr('for', 'projectStatusUpdateDescription')										.text('Description:')								).append(									$(' ')										.attr({ name: 'projectStatusUpdateDescription', id: 'projectStatusUpdateDescription', style: 'width:98.5% !important' }).css({ padding: '5px', fontSize: '12px', height: '130px' // TODO: remove this after testing }).html(testTxt)								) )				).append(					$(' ')						.attr('id', 'projectStatusPreview')						.css({ backgroundColor: '#FDFFE7', border: '1px solid #FCEB92', padding: '5px', height: '310px', overflowY: 'scroll', marginBottom: '5px' }).hide				)/* .append(					$(' ')						.attr({'id': 'projectStatusBackBtn'})						.text('Back')						.click(function (e) { e.preventDefault; _this.back; }).hide				).append(					$(' ')						.attr({'id': 'projectStatusPreviewBtn'})						.text('Preview')						.click(function (e) { e.preventDefault; _this.preview; })				).append(					$(' ')						.attr({'id': ''})						.text('Publish')						.click(function (e) { e.preventDefault; _this.publish; })				)*/;		this.project = { date: '', name: '', desc: '' };

var projectPages = [ 'Wikimedia_Features_engineering', 'Wikimedia_Platform_Engineering', 'Wikimedia_Mobile_engineering' ];		//		var lookFor = '{{Wikimedia project index line|'; // Testing getPages this.getPages( projectPages, function (pages) {			for (var page in pages) {				_this.scrubPage( pages[page], lookFor, buildProjectsObject );			}		} );		function buildProjectsObject( title, names ) { var $projectSel = $modalElements.find('#projectStatusName'); var $projectGroup = $(' ').attr('label', title); var name;

for (var i = 0; i < names.length; i++) { name = names[i] .replace(lookFor, '') .replace('}}', ''); $projectGroup .append(						$(' ')							.attr('value', name)							.text(name)				); }			$projectSel.append($projectGroup); }		// add to the dom $('body').append(			$(' ').attr({ id: 'psh-dialog', title: 'Project Status Helper' })			.append( $modalElements )			.hide		); mw.loader.using(['jquery.ui.dialog', 'jquery.ui.datepicker'], function {			$( '#psh-dialog' ).dialog({ height: 'auto', width: 800, modal: true, buttons: [ {						id: 'projectStatusBackBtn', text:'Back', click: function { _this.back; }					},					{						id: 'projectStatusPreviewBtn', text: 'Preview', click: function { _this.preview; }					},					{						id: 'projectStatusPublishBtn', text: 'Publish', click: function { _this.publish; }					}				]			});			// hide preview back button			$('#projectStatusBackBtn').hide;			// datepicker that			$('#projectStatusDate')				.datepicker({'dateFormat': 'yy-mm-dd'});			var thistitle = mw.config.get('wgTitle');			$('#projectStatusName').val(thistitle);			_this.updateStatusLink;

});		this.$modal = $('#psh-dialog');		return this;	};

Psh.prototype.scrubPage = function (page, lookFor, callback) { var lines, i;		var found = []; var content = page.revisions[0]['*']; var title = page.title;

if ( content.length > 0 ) { lines = content.split(/\n/); // Loop through lines for ( i = 0; i < lines.length; i++ ) { if ( lines[i].indexOf( lookFor ) !== -1 ) { found.push(						lines[i]					); }			}			callback ( title, found ); }	};

Psh.prototype.addStatus = function { var _this = this, summary = 'new status update';

// Send the wikitext to the parser for rendering $.ajax({			url: mw.util.wikiScript( 'api' ),			data: {				action: 'edit',				// Project page name...				title: this.pageName,				//appendtext: this.wikitext,				text: this.wikitext,				token: mw.user.tokens.get( 'editToken' ),				format: 'json',				summary: summary,				notminor: true			},			dataType: 'json',			type: 'POST',			success: function ( data ) {				//goto the page for now				if (data && data.edit && data.edit.result === 'Success') {					_this.hideSpinner;					window.location.href = _this.pageURL;				}			},			error: function {}		}); };

Psh.prototype.getPages = function (titles, callback) { var pagesQuery = $.isArray(titles) ? titles.join('|') : titles; $.ajax({			url: mw.util.wikiScript( 'api' ),			data: {				action: 'query',				format: 'json',				titles: pagesQuery,				prop: 'revisions',				rvprop: 'content'			},			dataType: 'json',			type: 'GET',			cache: 'false',			success: function (data) {				if ( data && data.query && data.query.pages ) {					// return pages to callback					if( typeof callback === 'function') {						callback( data.query.pages );					}				}			},			error: function {				if( typeof callback === 'function') {					callback( {} );				}			}		});

};	/* quick and dirty validation form */ Psh.prototype.validate = function { for (var prop in this.project) { if (this.project[prop] === '') { return false; }		}		return true; };

/* query for page */ Psh.prototype.publish = function { var _this = this;

this.showSpinner; this.setupProject; console.log(this.project); if (this.validate === false) { alert('Project name, date, and description are required.'); this.hideSpinner; return ; }		this.getPages( this.pageName, function ( pages ) {			// use only first page in pages object.			for (var page in pages ) {				//if page exists the title will be in the returned object				if ( pages[page].hasOwnProperty('revisions') ) {					//save wikitext, if page empty pageContent will be 					_this.pageContent = pages[page].revisions[0]['*'];				} else {					// page does not exist					_this.pageContent = ;				}				_this.prepareContentAndUpdate;				break;			}		}); };	Psh.prototype.setupProject = function { var $helperForm = this.$modal.find('#projectStatusHelperForm');

this.project = { date: $helperForm.find('#projectStatusDate').val, name: $helperForm.find('#projectStatusName').val, monthly: $helperForm.find('#projectStatusMonthlyFlag').prop('checked'), desc: $helperForm.find('#projectStatusUpdateDescription').val };		//if monthly status update if (this.project.monthly) { this.project.date = this.project.date.substring(0, 8) + 'monthly'; }		this.project.latestUpdate = 'Last update on: ' + this.project.date + ' '; this.wikitext = ''; };

Psh.prototype.prepareContentAndUpdate = function { var statusLines = [], lookFor =''; // for replacement, gather text before and after entry var splitentry = {'before':, 'middle':, 'after':''}; var splitstate = 'before'; var replace = false;

if ( this.pageContent.length > 0 ) { statusLines = this.pageContent.split(/\n/); var datematch = null;

//loop through status lines for ( var i=0; i<statusLines.length; i++ ) { datematch = statusLines[i].match( /^== *([^= ]*) *==$/ ); if ( datematch != null ) { if ( datematch[1] == this.project.date ) { if (confirm('Replace existing entry?')) { replace = true; splitstate = 'middle'; } else { console.log('abort'); this.hideSpinner; return ; }					}					else if ( splitstate == 'middle' ) { // we must be starting a new section splitstate = 'after'; }

}				splitentry[splitstate] += statusLines[i] + '\n'; }			// replace last update with new update splitentry['before'] = splitentry['before'].replace( statusLines[0], this.project.latestUpdate ); } else { //new page, place last update message above wikitext splitentry['before'] = this.project.latestUpdate; }		this.wikitext = splitentry['before']; if( splitentry['middle'] == '' ) { this.wikitext += '\n\n'; }		this.wikitext += this.buildWikitext; this.wikitext += splitentry['after']; this.addStatus; };

Psh.prototype.buildWikitext = function { var retval = '' retval = '== ' + this.project.date + ' ==\n\n'; retval += '' + this.project.desc + ''; retval += '\n\n'; return retval; };

Psh.prototype.preview = function { var _this = this;

$('#projectStatusPreviewBtn').hide; $('#projectStatusBackBtn').show;

this.showSpinner; this.setupProject; this.buildWikitext;

// Send the wikitext to the parser for rendering $.ajax({			url: mw.util.wikiScript( 'api' ),			data: {				'action': 'parse',				'title': this.pageName,				'format': 'json',				'text': this.wikitext,				'prop': 'text',				'pst': true			},			dataType: 'json',			type: 'POST',			success: function ( data ) {				_this.$modal.find('#projectStatusPreview')					.html( data.parse.text['*'] )					.show					.prev					.hide;				_this.$modal.find('.editsection').remove;				_this.hideSpinner;			},			error: function {}		}); };

Psh.prototype.back = function { $('#projectStatusHelperInput, #projectStatusPreviewBtn').show; $('#projectStatusPreview, #projectStatusBackBtn').hide; };

Psh.prototype.showSpinner = function { this.$modal .find('.mw-ajax-loader') .show; };

Psh.prototype.hideSpinner = function { this.$modal .find('.mw-ajax-loader') .hide; };

Psh.prototype.updateStatusLink = function { var path = mw.config.get('wgServer') + mw.config.get('wgArticlePath'), $inputElement = this.$modal .find('#projectStatusName'); var selectedProject = $inputElement.val;

if(selectedProject != '') { this.pageName = $inputElement.val + '/status'; this.pageURL = path.replace( '$1', this.pageName );

$('#projectLink') .attr( 'href', this.pageURL ) .text( this.pageName ); }

};

$(document).ready(function {		// Add a link to the toolbox		var link = mw.util.addPortletLink( 'p-tb', '#',			'Project Status', 't-prettylinkwidget', 'Dialog to help you submit your project updates', null, '#t-projectstatushelper' );		// Setup link click event		$(link).click(function { var psh = new Psh; $('#projectStatusHelperInput div').css({				'margin-top': '10px'			}); });

}); })( mediaWiki, jQuery );