MediaWiki:Gadget-WmfProjectStatusHelper.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 )										.bind( 'change', function { _this.fillDescriptionField; })								).append(									$( ' ' ).text( 'eg. YYYY-MM-DD' )								) ).append( $( ' ' )								.append(									$( ' ' )										.attr( 'for', 'projectStatusMonthlyFlag' )										.text( 'Include in Monthly report?' )								).append(									$( ' ' )										.attr({ type: 'checkbox', name: 'projectStatusMonthlyFlag', id: 'projectStatusMonthlyFlag' })										.bind( 'change', function { _this.fillDescriptionField; })								)						).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: '' };		this.uneditedDescription = '';

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 );			}			_this.updateStatusLink;			_this.fillDescriptionField;		} ); function buildProjectsObject( title, names ) { var $projectSel = $modalElements.find( '#projectStatusName' ); var $projectGroup = $( ' ' ).attr( 'label', title ); var name;

var projectFromPage = mw.config.get( 'wgTitle' ).replace( '/status', '' ); for ( var i = 0; i < names.length; i++ ) { name = names[i] .replace( lookFor, '' ) .replace( '}}', '' ); $projectGroup .append(						$( ' ' )							.attr( 'value', name )							.prop( 'selected', name === projectFromPage )							.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'});		}); 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 ) {				// go to 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; };

Psh.prototype.fillDescriptionField = function { var _this = this; var $descField = _this.$modal.find( '#projectStatusUpdateDescription' ); var currentdesc = $descField.val; if ( this.uneditedDescription != currentdesc && currentdesc != '' ) { if ( !confirm( 'Replace existing entry with content from date (if available)?' ) ) { return false; }		}

this.setupProject; if ( this.project.name == '' ) { _this.pageContent = ''; return false; }		this.getPages( this.pageName, function( pages ) {			_this.fillStatusPageContent( pages );			var splitEntry = _this.getStatusPageSplitAtDate( _this.project.date );			var desc = _this.stripEntry( splitEntry['middle'] );			$descField.val( desc );			_this.uneditedDescription = desc;		}); }

Psh.prototype.stripEntry = function( content ) { // strip the title content = content.replace( /^== *([^= ]*) *==\n\n?/m, '' ); // ...and the begin and end tags content = content.replace( /^\s*]*\/>\s*\n?/mg, '' ); content = content.replace( /]*\/>\s*\n?$/mg, '' ); return content; }

Psh.prototype.fillStatusPageContent = 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 = ''; }			break; }	};

/* 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 ) {			_this.fillStatusPageContent( pages );			_this.prepareContentAndUpdate;		}); };	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.getStatusPageSplitAtDate = function( splitDate ) { var statusLines = [], lookFor = ''; // for replacement, gather text before and after entry var splitEntry = { 'firstline': '', 'before': '', 'middle': '', 'after': '' };

if ( this.pageContent.length > 0 ) { statusLines = this.pageContent.split( /\n/ ); splitEntry['firstline'] = statusLines[0] + '\n';

var datematch = null; var splitState = 'before';

//skip the first line, populate 'before', 'middle', and 'after' for ( var i = 1; i < statusLines.length; i++ ) { datematch = statusLines[i].match( /^== *([^= ]*) *==$/ ); if ( datematch != null ) { if ( datematch[1] == splitDate ) { splitState = 'middle'; } else if ( splitState == 'middle' ) { // we must be starting a new section splitState = 'after'; }

}				splitEntry[splitState] += statusLines[i] + '\n'; }		}		return splitEntry; };

/**	 * replace this.wikitext with new version containing latest update, * then push it to the wiki. */	Psh.prototype.prepareContentAndUpdate = function { var splitEntry = this.getStatusPageSplitAtDate( this.project.date ); if( splitEntry['middle'] != '' ) { if ( confirm( 'Replace existing entry?' ) ) { splitstate = 'middle'; } else { //console.log( 'abort' ); this.hideSpinner; return false; }		}		this.wikitext = this.project.latestUpdate + '\n'; this.wikitext += splitEntry['before']; if( splitEntry['middle'] == '' ) { this.wikitext += '\n\n'; }		this.wikitext += this.buildWikitext; this.wikitext += splitEntry['after']; this.addStatus; return true; };

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; wikitext = 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': 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( e ) { e.preventDefault; var psh = new Psh; $( '#projectStatusHelperInput div' ).css({				'margin-top': '10px'			}); });

}); })( mediaWiki, jQuery );