MediaWiki:Gadget-wrcAddNewGroup.js

From Outreach Wiki
Jump to navigation Jump to search

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
/**
 * Gadget (based on JavaScript) to add new group (ANG) to 
 * [[Module:Wikimedia Resource Center/Groups]] on the Wikimedia 
 * Resource Center (WRC).
 */
( function () {
	'use strict';
	
	if ( mw.config.values.wgPageName.split('/')[0] == 'Connect' ) {
		
		mw.loader.using( [
			'ext.gadget.luaparse',
			'mediawiki.api',
			'oojs-ui',
			'oojs-ui-core',
			'oojs-ui.styles.icons-editing-core'
		] ).done( function () {
			var cleanRawEntry, gadgetMsg, getContentGroups,
				getContentModule, getRelevantRawEntry, openWindow,
				userLang;
	
			userLang = mw.config.get( 'wgUserLanguage' );
			if ( userLang == 'en' ) {
				userLang = 'en-foo'; // quick hack fix
			}
			/**
			 * Query the I18n Template for keywords to be used in this gadget.
			 * NOTE: The strings must be marked for translation before the keys/value 
			 * pair works in the gadget.
			 */
			new mw.Api().get( {
				action: 'query',
				list: 'messagecollection',
				mcgroup: 'page-Template:I18n/Wikimedia_Resource_Center',
				mclanguage: userLang
			} ).done( function ( data ) {
				var i, res, key, val, gadgetMsg = {};
				res = data.query.messagecollection;
				for ( i = 0; i < res.length; i++ ) {
					key = res[ i ].key.replace( 'Template:I18n/Wikimedia_Resource_Center/', '' );
					val = res[ i ].translation;
					if ( !val ) {
						// No translation; fall back to English
						val = res[ i ].definition;
					}
					gadgetMsg[ key ] = val;
				}
				
				/**
				 * Provides API parameters for getting the content of the 
				 * [[Module:Wikimedia Resource Center/Groups]] page
				 *
				 * @return {Object}
				 */
				getContentGroups = function () {
					return {
						action: 'query',
						prop: 'revisions',
						titles: 'Module:Wikimedia Resource Center/Groups',
						rvprop: 'content',
						rvlimit: 1
					};
				};
				
				/**
				 * Loops through the API response and returns a specific requested entry
				 *
				 * @param {Object} entries API response.
				 * @param {string} username The entry we want to pick out.
				 */
				getRelevantRawEntry = function ( entries, name ) {
					var i, j;
					// Look through the individual entries
					for ( i = 0; i < entries.length; i++ ) {
						// Loop through the individual key-value pairs within each entry
						for ( j = 0; j < entries[ i ].value.fields.length; j++ ) {
							if (
								entries[ i ].value.fields[ j ].key.name == 'name' &&
								entries[ i ].value.fields[ j ].value.value == name
							) {
								return entries[ i ].value.fields;
							}
						}
					}
				};
				
				/**
				  * Take a raw entry from the abstract syntax tree and make it an object
				  * that is easier to work with.
				  *
				  * @param {Object} relevantRawEntry the raw entry from the AST
				  * @return {Object} The cleaned up object
				  */
				cleanRawEntry = function ( relevantRawEntry ) {
					var entryData = {},
						i, j;
					for ( i = 0; i < relevantRawEntry.length; i++ ) {
						entryData[ relevantRawEntry[ i ].key.name ] = relevantRawEntry[ i ].value.value;
					}
					return entryData;
				};
				
				/**
				 * Get an entire content of the [[Module:Wikimedia Resource Center/Groups]] page
				 *
				 * @param {Object} sourceblob The original API return
				 * @return {Object} raw An Abstract Syntax Tree
				 */
				getContentModule = function ( sourceblob ) {
					var i, raw, ast;
					for ( i in sourceblob ) {  // should only be one result
						raw = sourceblob[ i ].revisions[ 0 ][ '*' ];
						ast = luaparse.parse( raw );
						return ast.body[ 0 ].arguments[ 0 ].fields;
					}
				};
	
				/**
				 * Subclass ProcessDialog
				 *
				 * @class WrcAddNewGroup
				 * @extends OO.ui.ProcessDialog
				 *
				 * @constructor
				 * @param {Object} config
				 */
				function WrcAddNewGroup( config ) {
					this.type = 'group'; // default here is group
					this.name = '';
					this.description = '';
					this.icon = '';
					this.facebook = '';
					this.twitter = '';
					this.facebook = '';
	
					if ( config.name ) {
						this.name = config.name;
					}
					if ( config.description ) {
						this.description = config.description;
					}
					if ( config.icon ) {
						this.icon = config.icon;
					}
					if ( config.facebook ) {
						this.facebook = config.facebook;
					}
					if ( config.twitter ) {
						this.twitter = config.twitter;
					}
					if ( config.youtube ) {
						this.youtube = config.youtube;
					}
					WrcAddNewGroup.super.call( this, config );
				}
				OO.inheritClass( WrcAddNewGroup, OO.ui.ProcessDialog );
	
				WrcAddNewGroup.static.name = 'wrcAddNewGroup';
				WrcAddNewGroup.static.title = gadgetMsg[ 'editor-ang-button' ];
				WrcAddNewGroup.static.actions = [
					{
						action: 'continue',
						modes: 'edit',
						label: gadgetMsg[ 'editor-save' ],
						flags: [ 'primary', 'constructive' ]
					},
					{
						action: 'cancel',
						modes: 'edit',
						label: gadgetMsg[ 'editor-cancel' ],
						flags: 'safe'
					}
				];
	
				/**
				 * Use the initialize() method to add content to the dialog's $body,
				 * to initialize widgets, and to set up event handlers.
				 */
				WrcAddNewGroup.prototype.initialize = function () {
					var dialog;
					dialog = this;
	
					WrcAddNewGroup.super.prototype.initialize.call( this );
					this.content = new OO.ui.PanelLayout( {
						padded: true,
						expanded: false
					} );
					this.fieldType = new OO.ui.TextInputWidget( {
						value: this.type,
						indicator: 'required',
						required: true
					} );
					this.fieldName = new OO.ui.TextInputWidget( {
						value: this.name,
						indicator: 'required',
						required: true
					} );
					this.fieldDescription = new OO.ui.MultilineTextInputWidget( {
						value: this.description,
						rows: 5,
						indicator: 'required',
						required: true
					} );
					this.fieldIcon = new OO.ui.TextInputWidget( {
						value: this.icon
					} );
					// Adding form fields to get social media links 
					this.fieldFacebook = new OO.ui.TextInputWidget( {
						value: this.facebook
					} );
					this.fieldTwitter = new OO.ui.TextInputWidget( {
						value: this.twitter
					} );
					this.fieldYouTube = new OO.ui.TextInputWidget( {
						value: this.youtube
					} );
					
					this.deleteButton = new OO.ui.ButtonWidget( {
						label: gadgetMsg[ 'editor-remove-entry' ],
						icon: 'trash',
						flags: [ 'destructive' ]
					} ).on( 'click', function () {
						new OO.ui.confirm(
							gadgetMsg[ 'editor-remove-confirm' ]
						).done( function ( confirmed ) {
							if ( confirmed ) {
								dialog.saveItem( 'delete' );
							}
						} );
					} );
					
					// Append things to fieldSet
					this.fieldSet = new OO.ui.FieldsetLayout( {
						items: [
							new OO.ui.FieldLayout(
								this.fieldName,
								{
									label: gadgetMsg[ 'editor-ang-fieldname' ],
									align: 'top',
									help: gadgetMsg[ 'editor-ang-fieldname-help' ]
								}
							),
							new OO.ui.FieldLayout(
								this.fieldDescription,
								{
									label: gadgetMsg[ 'editor-ang-fielddescription' ],
									align: 'top',
									help: gadgetMsg[ 'editor-ang-fielddescription-help' ]
								}
							),
							new OO.ui.FieldLayout(
								this.fieldIcon,
								{
									label: gadgetMsg[ 'editor-ang-fieldicon' ],
									align: 'top',
									help: gadgetMsg[ 'editor-ang-fieldicon-help' ]
								}
							),
							new OO.ui.FieldLayout(
								this.fieldFacebook,
								{
									label: gadgetMsg[ 'editor-ang-fieldfacebook' ],
									align: 'top',
									help: gadgetMsg[ 'editor-ang-fieldfacebook-help' ]
								}
							),
							new OO.ui.FieldLayout(
								this.fieldTwitter,
								{
									label: gadgetMsg[ 'editor-ang-fieldtwitter' ],
									align: 'top',
									help: gadgetMsg[ 'editor-ang-fieldtwitter-help' ]
								}
							),
							new OO.ui.FieldLayout(
								this.fieldYouTube,
								{
									label: gadgetMsg[ 'editor-ang-fieldyoutube' ],
									align: 'top',
									help: gadgetMsg[ 'editor-ang-fieldyoutube-help' ]
								}
							)
						]
					} );
					
					if ( this.name ) {
						this.fieldSet.addItems( [
							new OO.ui.FieldLayout(
								this.deleteButton
							)
						] );
					}
					
					// When everything is done
					this.content.$element.append( this.fieldSet.$element );
					this.$body.append( this.content.$element );
				};
	
				/**
				 * Set custom height for the modal window
				 *
				 */
				WrcAddNewGroup.prototype.getBodyHeight = function () {
					return 550; // NOTE: Remember to add height when new fields are added
				};
	
				/**
				 * In the event "Select" is pressed
				 */
				WrcAddNewGroup.prototype.getActionProcess = function ( action ) {
					var dialog = this;
					if ( action === 'continue' && dialog.fieldType.getValue() ) {
						return new OO.ui.Process( function () {
							dialog.saveItem();
						} );
					} else {
						return new OO.ui.Process( function () {
							dialog.close();
						} );
					}
					return NewItemDialog.parent.prototype.getActionProcess.call( this, action );
				};
	
				/**
				 * Save the changes to [[Module:Wikimedia Resource Center/Groups]] page.
				 */
				WrcAddNewGroup.prototype.saveItem = function ( deleteFlag ) {
					var dialog = this, content, insertGroupPage = '',
					generateGrouppageData, gpName;
	
					dialog.pushPending();
	
					new mw.Api().get( getContentGroups() ).done( function ( data ) {
						var i, insertInPlace, sanitizeInput, processWorkingEntry,
							editSummary, manifest = [], workingEntry,
							generateKeyValuePair, entries, pageName;
							
						/**
						  * Sanitizes input for saving to wiki
						  *
						  * @param {string} s
						  *
						  * @return {string}
						  */
						sanitizeInput = function ( s ) {
							return s
								.replace( /\\/g, '\\\\' )
							.replace( /\n/g, '<br />' );
						};
	
						/**
						  * Creates Lua-style key-value pairs, including converting the
						  * audiences array into a proper sequential table.
						  *
						  * @param {string} k The key
						  * @param {string} v The value
						  *
						  * @return {string}
						  */
						generateKeyValuePair = function ( k, v ) {
							var res, jsonarray;
							res = '\t\t'.concat( k, ' = ' );
							v = sanitizeInput( v );
							v = v.replace( /'/g, '\\\'' );
							res += '\'' + v + '\'';
							res += ',\n';
							
							return res;
						};
						
						/**
						 * Generate group's page data to use in creating the group page 
						 */
						generateGrouppageData = function ( k, v ) {
							var res;
							res = '| '.concat( k, ' = ');
							res += v + '\n';
							
							return res;
						};
						
						/**
						 * Compares a given Wikimedia Resource Center entry against the
						 * edit fields and applies changes where necessary. Also, build the 
						 * the group page Template here in order to create the group.
						 *
						 * @param {Object} workingEntry the entry being worked on
						 * @return {Object} The same entry but with modifications
						 */
						processWorkingEntry = function ( workingEntry ) {
							workingEntry.type = dialog.fieldType.getValue();
							// Generate group page data in template form
							insertGroupPage = '{{Connect group\n';
	
							if ( dialog.fieldType.getValue() ) {
								workingEntry.type = dialog.fieldType.getValue();
							} else if ( !dialog.fieldType.getValue() && workingEntry.type ) {
								delete workingEntry.type;
							}
							
							if ( dialog.fieldName.getValue() ) {
								workingEntry.name = dialog.fieldName.getValue();
							} else if ( !dialog.fieldName.getValue() && workingEntry.name ) {
								delete workingEntry.name;
							}
	
							if ( dialog.fieldDescription.getValue() ) {
								workingEntry.description = dialog.fieldDescription.getValue();
								insertGroupPage += generateGrouppageData(
									'introduction',
									workingEntry.description
								);
							} else if ( !dialog.fieldDescription.getValue() && workingEntry.description ) {
								delete workingEntry.description;
							}
	
							if ( dialog.fieldIcon.getValue() ) {
								workingEntry.icon = dialog.fieldIcon.getValue();
								insertGroupPage += generateGrouppageData(
									'icon',
									workingEntry.icon
								);
							} else if ( !dialog.fieldIcon.getValue() && workingEntry.icon ) {
								delete workingEntry.icon;
							}
							
							if ( dialog.fieldFacebook.getValue() ) {
								workingEntry.facebook = dialog.fieldFacebook.getValue();
								insertGroupPage += generateGrouppageData(
									'facebook',
									workingEntry.facebook
								);
							} else if ( !dialog.fieldFacebook.getValue() && workingEntry.facebook ) {
								delete workingEntry.facebook;
							}
							
							if ( dialog.fieldTwitter.getValue() ) {
								workingEntry.twitter = dialog.fieldTwitter.getValue();
								insertGroupPage += generateGrouppageData(
									'twitter',
									workingEntry.twitter
								);
							} else if ( !dialog.fieldTwitter.getValue() && workingEntry.twitter ) {
								delete workingEntry.twitter;
							}
							
							if ( dialog.fieldYouTube.getValue() ) {
								workingEntry.youtube = dialog.fieldYouTube.getValue();
								insertGroupPage += generateGrouppageData(
									'youtube',
									workingEntry.youtube
								);
							} else if ( !dialog.fieldYouTube.getValue() && workingEntry.youtube ) {
								delete workingEntry.youtube;
							}
							
							// finish the page data
							insertGroupPage += '}}\n\n[[Category:Connect groups|{{SUBPAGENAME}}]]';
	
							return workingEntry;
						};
						
						// Cycle through existing entries. If we are editing an existing
						// entry, that entry will be modified in place.
						entries = getContentModule( data.query.pages );
	
						for ( i = 0; i < entries.length; i++ ) {
							workingEntry = cleanRawEntry( entries[ i ].value.fields );
							if ( workingEntry.name == dialog.name ) {
								if ( deleteFlag ) {
									editSummary = 'Removing entry: '.concat( workingEntry.name );
								} else {
									workingEntry = processWorkingEntry( workingEntry );
									editSummary = 'Editing entry: '.concat( workingEntry.name );
								}
							}
							if ( workingEntry.name != dialog.name || !deleteFlag ) {
								manifest.push( workingEntry );
							}
						}
						
						// No unique group name means this is a new entry
						if ( !dialog.name ) {
							workingEntry = {};
							workingEntry = processWorkingEntry( workingEntry );
							editSummary = gadgetMsg[ 'editor-ang-preeditsummary' ].concat( workingEntry.name );
							manifest.push( workingEntry );
						}
						
						// Re-generate the Lua table based on `manifest`
						
						insertInPlace = 'return {\n';
						
						for ( i = 0; i < manifest.length; i++ ) {
							insertInPlace += '\t{\n';
							if ( manifest[ i ].type ) {
								insertInPlace += generateKeyValuePair(
									'type',
									manifest[ i ].type
								);
							}
							if ( manifest[ i ].name ) {
								insertInPlace += generateKeyValuePair(
									'name',
									manifest[ i ].name.split(" ").join("_")
								);
								// Save the page name
								pageName = 'Connect/' + manifest[ i ].name;
								gpName = manifest[ i ].name.split(" ").join("_");
							}
							if ( manifest[ i ].description ) {
								insertInPlace += generateKeyValuePair(
									'description',
									manifest[ i ].description
								);
							}
							if ( manifest[ i ].icon ) {
								insertInPlace += generateKeyValuePair(
									'icon',
									manifest[ i ].icon
								);
							}
							if ( manifest[ i ].facebook ) {
								insertInPlace += generateKeyValuePair(
									'facebook',
									manifest[ i ].facebook
								);
							}
							if ( manifest[ i ].twitter ) {
								insertInPlace += generateKeyValuePair(
									'twitter',
									manifest[ i ].twitter
								);
							}
							if ( manifest[ i ].youtube ) {
								insertInPlace += generateKeyValuePair(
									'youtube',
									manifest[ i ].youtube
								);
							}
							insertInPlace += '\t},\n';
						}
						insertInPlace += '}';
						
						new mw.Api().postWithToken(
							'csrf',
							{
								action: 'edit',
								nocreate: true,
								summary: editSummary,
								pageid: 10588351,  // Module:Wikimedia_Resource_Center/Groups
								text: insertInPlace,
								contentmodel: 'Scribunto'
							}
						).done( function () {
							dialog.close();
							// Purge the cache of the page from which the edit was made
							new mw.Api().postWithToken(
								'csrf',
								{ action: 'purge', titles: mw.config.values.wgPageName }
							).done( function () {
								location.reload();
							} );
						} ).fail( function () {
							alert( gadgetMsg[ 'editor-ang-failed-alert' ] );
							dialog.close();
						} );
						
						// Now create the group page as sub-page of connect
						new mw.Api().postWithToken(
							'csrf',
							{
								action: 'edit',
								title: pageName,  // Page name as sub-page of Connect
								summary: editSummary,
								text: insertGroupPage,
								contentmodel: 'wikitext'
							}
						).done( function () {
							dialog.close();
							// Purge the cache of the page from which the edit was made
							new mw.Api().postWithToken(
								'csrf',
								{ action: 'purge', titles: mw.config.values.wgPageName }
							).done( function () {
								location.reload();
							} );
						} ).fail( function () {
							alert( gadgetMsg[ 'editor-ang-failed-alert' ] );
							dialog.close();
						} );
					} );
				};
				
				/**
				* Event handler for when someone clicks on an edit icon/button
				*
				* @param {Object} config
				*/
				openWindow = function ( config ) {
					var wrcAddNewGroup, windowManager;
					config.size = 'large';
					wrcAddNewGroup = new WrcAddNewGroup( config );
	
					windowManager = new OO.ui.WindowManager();
					$( 'body' ).append( windowManager.$element );
					windowManager.addWindows( [ wrcAddNewGroup ] );
					windowManager.openWindow( wrcAddNewGroup );
				};
				
				// Edit user content via the form
				$( '.group-edit-icon' ).each( function () {
					var $icon = $( this ),
						editButton;
					editButton = new OO.ui.ButtonWidget( {
						framed: false,
						icon: 'edit'
					} ).on( 'click', function () {
						new mw.Api().get( getContentGroups() ).done( function ( data ) {
							var entryData, gpName, content;
							
							gpName = editButton.$element
								.closest( '.wrc-card' )
								.data( 'wrc-unique-id' );
								
							entryData = cleanRawEntry(
								getRelevantRawEntry(
									getContentModule( data.query.pages ),
									gpName
								)
							);
							openWindow( entryData );
						} );
					} );
					$icon.css( 'float', 'right' );
					$icon.append( editButton.$element );
				} );
				
				// Open dialog when "Add new group" is clicked
				$( '.wrc-ag-button' ).each( function () {
					var $addButton = $( this ),
						addButton;
					addButton = new OO.ui.ButtonWidget( {
						icon: 'add',
						label: gadgetMsg[ 'editor-ang-button' ],
						flags: [ 'primary', 'progressive' ]
					} ).on( 'click', function () {
						openWindow( {} );
					} );
					$addButton.css( 'margin', '1.125em' );
					$addButton.append( addButton.$element );
				} );
			} );
		} );
	}
}() );