MediaWiki:Gadget-WmfProjectStatusHelper.js

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

TODO: Conflict handling: Edit, Replace, Cleanup

var testTxt = ''; ( function( mw, $ ) {

/* Project status helper class */ Psh = function( project ) { this.project = { date: '', name: '', desc: '' };		if( project !== undefined && project.date !== undefined ) { this.project.date = project.date; } else { this.project.date = ( new Date ).toJSON.substring( 0, 10 ); }		if( project !== undefined && project.name !== undefined ) { this.project.name = project.name; } else { this.project.name = mw.config.get( 'wgTitle' ).replace( '/status', '' ); }

var	_this = this, todayFormatted = this.project.date.replace( 'monthly', '28' ), isMonthly = ( this.project.date.search( 'monthly' ) > -1 ), $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' })										.on( '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 )										.on( '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' })										.prop( 'checked', isMonthly )										.on( '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.uneditedDescription = '';

var projectPages = [ 'Wikimedia_Features_engineering', 'Wikimedia_Platform_Engineering', 'Wikimedia_mobile_engineering', 'Analytics', 'Wikimedia_Language_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;

for ( var i = 0; i < names.length; i++ ) { name = names[i] .replace( lookFor, '' ) .replace( '}}', '' ); $projectGroup .append(						$(' ')							.attr( 'value', name )							.prop( 'selected', name === _this.project.name )							.text( name )				); }			$projectSel.append( $projectGroup ); }		$( '#psh-dialog' ).remove; // add to the DOM $( 'body' ).append(			$( ' ' ).attr({ id: 'psh-dialog', title: 'Project Status Helper' })			.append( $modalElements )			.hide		);

mw.loader.using( ['jquery.ui'], 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( 'csrfToken' ),				format: 'json',				summary: summary,				notminor: true			},			dataType: 'json',			type: 'POST',			success: function( data ) {				if ( data && data.edit && data.edit.result === 'Success' ) {					_this.hideSpinner;					window.location.reload( true );				}			},			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 = this.wikitext.replace( /\n*$/m, '\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'			}); });

// override edit links on project pages $( '.mw-statushelper-editlink > a' ).each( function( i ) {			$( this ).click( function( event ) { event.preventDefault; var projectObj = {}; projectObj.name = $( '.mw-statushelper-editlink' )[i].getAttribute( 'data-statuspage' ).replace( '/status', '' ); projectObj.date = $( '.mw-statushelper-editlink' )[i].getAttribute( 'data-entrydate' ); var psh = new Psh( projectObj ); $( '#projectStatusHelperInput div' ).css({					'margin-top': '10px'				}); });		});

// override add links on project pages $( '.mw-statushelper-addlink > a' ).each( function( i ) {			$( this ).click( function( event ) { event.preventDefault; var projectObj = {}; projectObj.name = $( '.mw-statushelper-addlink' )[i].getAttribute( 'data-statuspage' ).replace( '/status', '' ); var psh = new Psh( projectObj ); $( '#projectStatusHelperInput div' ).css({					'margin-top': '10px'				}); });		});	}); })( mediaWiki, jQuery );