User:Mooeypoo/articlesandbox.js

var ARTICLE_REPO = mw.config.get( 'articlesandbox-article-repo' ) || '', NAME_FOR_SANDBOX = mw.config.get( 'articlesandbox-article-nameforsandbox' ) || 'sandbox', /**	 * Build the article structure from the API */	buildArticleStructure = function { var path, flatPath, articles = {};

if ( !ARTICLE_REPO ) { return $.Deferred.reject( 'noconfig' ); }

return ( new mw.Api ).get( {			action: 'query',			format: 'json',			list: 'prefixsearch',			pssearch: ARTICLE_REPO		} ) .then(			// Success			function ( queryData ) {				var articlesArr = queryData.query.prefixsearch;

articlesArr.forEach( function ( articleData ) {					var pieces = [],						title = articleData.title;

if ( title.indexOf( ARTICLE_REPO ) !== 0 ) { return; }

pieces = title.replace( ARTICLE_REPO, '' ).split( '/' ); pieces = pieces.filter( function ( piece ) {						return !!piece; // Get rid of empty strings					} );

// Build article structure path = articles; flatPath = ''; pieces.forEach( function ( piece ) {						flatPath += '/' + piece;

path[ piece ] = path[ piece ] || { _path: ARTICLE_REPO + flatPath, _articles: {} };

// Shift reference path = path[piece]._articles; } );				} );

if ( $.isEmptyObject( articles ) ) { return $.Deferred.reject( 'noarticles' ); }

return articles; },			// Convert failure to empty success function { return $.Deferred.reject( 'noarticles' ); }		);	},	collectSectionPageNames = function( articles ) {		var result = [];		$.each( articles, function ( page, data ) { if ( !$.isEmptyObject( articles[ page ]._articles ) ) { // This is a section result.push( articles[ page ]._path );

// Recurse, so we find sub pages that are sections collectSectionPageNames( articles[ page ]._articles ); }		} );

return result; },	/**	 * Get the page extracts from an API result *	 * @param {Object} apiResult An object coming from the API * @return {jQuery} A jQuery object with the extract content */	getPageExtractFromAPIResult = function ( apiResult ) { var apiPages = apiResult.query.pages, extract = apiPages[ Object.keys( apiPages )[ 0 ] ].extract || '';

return $( $.parseHTML( extract ) ); },	/**	 * Get the relevant content pieces from section pages */	getContentFromPage = function( page ) { return ( new mw.Api ).get( {			action: 'query',			format: 'json',			prop: 'extracts',			exsentences: 5,			titles: page		} ) .then(			getPageExtractFromAPIResult,			// Failure			function {				// Convert failure to success with an empty result				return $.Deferred.promise.resolve( '' );			}		); },	// Language messages = { he: { 'articlesandbox-title': 'אשף ערכים', 'articlesandbox-button': 'אשף ערכים', 'articlesandbox-dismiss': 'ביטול', 'articlesandbox-moreinfo': 'מידע נוסף', 'articlesandbox-goback': 'חזרה למסך הקודם', 'articlesandbox-create-article': ' ערך', 'articlesandbox-create-article-for': ' ערך עבור $1', 'articlesandbox-sections': 'רשימת ערכים', 'articlesandbox-create-button': ' ערך', 'articlesandbox-create-input-placeholder': 'כותרת הערך', 'articlesandbox-create-articlesummary': 'נוצר באמצעות |אשף הערכים', 'articlesandbox-error-badtitle': 'כותרת הערך שבחרת אינה תקינה. אנא שוב.' },		en: { 'articlesandbox-title': 'Article helper', 'articlesandbox-button': 'Article helper', 'articlesandbox-dismiss': 'Dismiss', 'articlesandbox-goback': 'Go back', 'articlesandbox-moreinfo': 'More info', 'articlesandbox-title-error': 'Error displaying contents', 'articlesandbox-create-article': 'Create article', 'articlesandbox-create-article-for': 'Create article for $1', 'articlesandbox-sections': 'Available article templates', 'articlesandbox-create-button': 'Create article', 'articlesandbox-create-input-placeholder': 'Article title', 'articlesandbox-create-articlesummary': 'Created by |ArticleSandbox user script', 'articlesandbox-error-missing-repoconfig': 'Cannot load article templates, since there is no repository configured. Please configure a base repository for all article templates through setting the mw.config entry \'articlesandbox-article-repo\' where you load this script, and try again.', 'articlesandbox-error-missing-articles': 'Cannot find any defined article templates. Please add articles to the repository, and try again.', 'articlesandbox-error-missing-articles-gotorepo': 'Go to the article repository', 'articlesandbox-error-badtitle': 'Invalid title: The title you chose contains invalid characters. Please retry.' }	};

// Set language, with default 'English' mw.messages.set( messages.en ); var lang = mw.config.get( 'wgUserLanguage' ); if ( lang && lang != 'en' && lang in messages ) { mw.messages.set( messages[ lang ] ); }

// Load the interface using the required module mw.loader.using( [ 'oojs-ui', 'mediawiki.api' ] ).then( function {	// Dialog settings; this must be inside the loader.using scope	// since it requires OOUI, and it must be on top to make sure	// the callers get its definition	var DialogPageLayout = function DialogPage( name, config ) {		DialogPageLayout.super.call( this, name, config );

this.titleWidget = new OO.ui.LabelWidget( {			label: config.title,			classes: [ 'articlesandbox-page-title' ]		} );

this.introWidget = new OO.ui.LabelWidget( {			label: config.intro,			classes: [ 'articlesandbox-page-intro' ]		} );

this.introLink = new OO.ui.ButtonWidget( {			label: mw.msg( 'articlesandbox-moreinfo' ),			icon: 'newWindow',			framed: false,			classes: [ 'articlesandbox-page-introlink' ],			target: '_blank'		} ),

this.$element.append(			this.titleWidget.$element,			this.introWidget.$element,			this.introLink.$element		);

if ( config.$content ) { this.$element.append( config.$content ); }

this.titleWidget.toggle( config.title ); this.introWidget.toggle( config.intro ); this.introLink.toggle( false ); };	OO.inheritClass( DialogPageLayout, OO.ui.PageLayout );

DialogPageLayout.prototype.setTitle = function ( title ) { this.titleWidget.setLabel( title ); this.titleWidget.toggle( title ); };	DialogPageLayout.prototype.setIntro = function ( intro, linkToReadMore ) { this.introWidget.setLabel( intro ); this.introLink.setHref( linkToReadMore );

this.introWidget.toggle( !!intro ); this.introLink.toggle( !!intro && !!linkToReadMore ); };

var ArticleItemWidget = function ArticleItemWidget( pageName, path, parentPath, indent ) { var i = 0;

ArticleItemWidget.super.call( this, { label: pageName, data: { path: path, parentPath: parentPath }, icon: 'article' } );

this.$element .addClass( 'articlesandbox-articleItemWidget' ) .css( { left: ( indent ) + 'em' } ) .toggleClass( 'articlesandbox-articleItemWidget-indent', indent ); };	OO.inheritClass( ArticleItemWidget, OO.ui.DecoratedOptionWidget ); var ArticleSectionWidget = function ArticleSectionWidget( pageName, indent, config ) { var i = 0;

ArticleSectionWidget.super.call( this, $.extend( { label: pageName }, config ) );

this.$element .addClass( 'articlesandbox-articleSectionWidget' ) .css( { left: ( indent ) + 'em' } ) .toggleClass( 'articlesandbox-articleSectionWidget-indent', indent ); };	OO.inheritClass( ArticleSectionWidget, OO.ui.MenuSectionOptionWidget ); /**	 * Article dialog *	 * @param {Object} [config] Configuration object */	var ArticleDialog = function ArticleDialog( config ) { ArticleDialog.super.call( this, config );

this.chosenItem = null; this.error = false; };	OO.inheritClass( ArticleDialog, OO.ui.ProcessDialog ); ArticleDialog.static.actions = [ { label: mw.msg( 'articlesandbox-dismiss' ), modes: ['articles', 'error', 'create'], flags: 'safe' }, { action: 'back', label: mw.msg( 'articlesandbox-goback' ), modes: 'create', flags: 'safe', icon: 'arrowPrevious' } ];	ArticleDialog.static.title = mw.msg( 'articlesandbox-title' ); ArticleDialog.static.name = 'articlehelperdialog'; ArticleDialog.static.size = 'large';

/**	 * Set error state for the dialog *	 * @param {string} [type='noarticles'] Error type; 'noconfig' or 'noarticles' */	ArticleDialog.prototype.setError = function ( type ) { var label = type === 'noconfig' ? mw.msg( 'articlesandbox-error-missing-repoconfig' ) : $( ' ' )				.append(					mw.msg( 'articlesandbox-error-missing-articles' ),					new OO.ui.ButtonWidget( { flags: [ 'progressive' ], icon: 'newWindow', label: mw.msg( 'articlesandbox-error-missing-articles-gotorepo' ), href: mw.config.get( 'wgArticlePath' ).replace( '$1', ARTICLE_REPO ), target: '_blank' } ).$element				);

this.error = true; this.errorLabel.setLabel( label ); this.showPage( 'error' ); };

/**	 * @inheritdoc */	ArticleDialog.prototype.initialize = function { var dialog = this;

ArticleDialog.super.prototype.initialize.apply( this, arguments );

this.$articlesPage = $( ' ' ) .addClass( 'articlesandbox-page-articles' ) this.$createPageForm = $( ' ' ) .addClass( 'articlesandbox-page-create-form' );

this.errorLabel = new OO.ui.LabelWidget( {			classes: [ 'articlesandbox-error' ]		} );

this.bookletLayout = new OO.ui.BookletLayout; this.bookletLayout.addPages( [			new DialogPageLayout( 'error', { title: mw.msg( 'articlesandbox-title-error' ), $content: this.errorLabel.$element } ),			new DialogPageLayout( 'articles', { title: mw.msg( 'articlesandbox-create-article' ), $content: this.$articlesPage } ),			new DialogPageLayout( 'create', { $content: $( ' ' ) .addClass( 'articlesandbox-page-create' ) .append( this.$createPageForm ) } )		] );

this.$body.append( this.bookletLayout.$element ); };

/**	 * @inheritdoc */	ArticleDialog.prototype.getActionProcess = function ( action ) { if ( action === 'back' ) { this.showPage( 'articles' ); }		return ArticleDialog.super.prototype.getActionProcess.call( this, action ); };

/**	 * @inheritdoc */	ArticleDialog.prototype.getSetupProcess = function ( data ) { return ArticleDialog.super.prototype.getSetupProcess.call( this, data ) .next( function {				if ( this.error ) {					this.showPage( 'error' );				} else {					this.showPage( 'articles' );				}	        }, this ); };

/**	 * Build the 'create page' form. * This will be updated with details from the item as it is chosen *	 * @param {OO.ui.OptionWidget} item Chosen item */	ArticleDialog.prototype.buildCreatePageForm = function ( item ) { var data = item.getData, makeHiddenInput = function ( name, value ) { return $( ' ' ) .attr( 'type', 'hidden' ) .attr( 'name', name ) .attr( 'value', value ); },			titleInput = new OO.ui.TextInputWidget( {				name: 'visibletitle',				// name: 'title',				placeholder: mw.msg( 'articlesandbox-create-input-placeholder' )			} ), errorLabel = new OO.ui.LabelWidget( {				classes: [ 'articlesandbox-create-titleerror' ],				label: mw.msg( 'articlesandbox-error-badtitle' )			} ), submit = new OO.ui.ButtonInputWidget( {				type: 'submit',				icon: 'add',				flags: [ 'progressive' ],				label: mw.msg( 'articlesandbox-create-button' ),			} ), $hiddenTitle = makeHiddenInput( 'title' ), // Mock a inputbox process $form = $( ' ' ) .attr( 'action', '/w/index.php' ) .attr( 'method', 'get' ) .append(					// makeHiddenInput( 'veaction', 'edit' ),					makeHiddenInput( 'action', 'edit' ),					$hiddenTitle,					makeHiddenInput( 'preload', data.path ),					makeHiddenInput( 'summary', mw.msg( 'articlesandbox-create-articlesummary' ) ),					makeHiddenInput( 'prefix', 'Special:MyPage/' + NAME_FOR_SANDBOX + '/' )				);

errorLabel.toggle( false );

titleInput.on( 'change', function ( val ) {			var valid = !!mw.Title.newFromText( val );

titleInput.setValidityFlag( valid ); submit.setDisabled( !valid ); errorLabel.toggle( !valid ); $hiddenTitle.attr( 'value', 'Special:MyPage/' + NAME_FOR_SANDBOX + '/' + titleInput.getValue ); } );

$form.append(			titleInput.$element,			errorLabel.$element,			submit.$element		);

// Reset the form and re-append it		this.$createPageForm.empty.append( $form ); };

/**	 * Show a specific page in the dialog *	 * @param {string} [name='articles'] Page name; 'articles', 'create' or 'error' */	ArticleDialog.prototype.showPage = function ( name ) { name = name || 'articles';

this.bookletLayout.setPage( name ); this.getActions.setMode( name ); };

/**	 * Respond to 'choose' event from any of the list widgets. *	 * @param {OO.ui.OptionWidget} item Chosen item */	ArticleDialog.prototype.onArticleListChoose = function ( item ) { var promise, dialog = this, data = item.getData;

this.chosenItem = item;

// Get the data from parent section if ( data.parentPath ) { promise = getContentFromPage( data.parentPath ) } else { promise = $.Deferred.resolve( '' ).promise; }

this.pushPending; promise.then( function ( $intro ) {			var currPage;

dialog.buildCreatePageForm( item ); dialog.showPage( 'create' );

currPage = dialog.bookletLayout.getCurrentPage; currPage.setTitle( mw.msg( 'articlesandbox-create-article-for', item.getLabel ) ); currPage.setIntro( $intro, mw.config.get( 'wgArticlePath' ).replace( '$1', data.parentPath ) );

dialog.popPending; } );	};

/**	 * Create a section for the article view from the article structure. * This takes the non-leaf nodes in the article structure, creates * a section and populates them with a listWidget that is full * of the subsequent leaf nodes, followed by any sub sections if * needed, recursively. *	 * @param {string} title Section title * @param {Object} articles Article structure object */	ArticleDialog.prototype.populateSection = function ( selectWidget, title, articles, sectionPath, indent ) { var dialog = this, items = [], pages = articles ? Object.keys( articles ) : [], sectionTitle = new ArticleSectionWidget( title, indent );

indent = indent || 0;

// Add title if ( title ) { selectWidget.addItems( [ sectionTitle ] ); }

// Look for sub sections and items pages.forEach( function ( page ) {			if ( !$.isEmptyObject( articles[ page ]._articles ) ) {				// This is a sub section, recurse				dialog.populateSection( selectWidget, page, articles[ page ]._articles, articles[ page ]._path, indent + 1 );			} else {				// This is a direct child				items.push( new ArticleItemWidget(						page,						articles[ page ]._path,						sectionPath,						indent					) );			}		} );

// Add children selectWidget.addItems( items ); };

/**	 * Build the content based on the article structure given *	 * @param {Object} articles Article structure with paths */	ArticleDialog.prototype.buildContent = function ( articles, $introContent ) { var dialog = this, $intro = $( ' ' ) .addClass( 'articlesandbox-main-intro' ), introLink = new OO.ui.ButtonWidget( {				label: mw.msg( 'articlesandbox-moreinfo' ),				icon: 'newWindow',				framed: false,				href: mw.config.get( 'wgArticlePath' ).replace( '$1', ARTICLE_REPO ),				target: '_blank'			} ), list = new OO.ui.SelectWidget( {				classes: [ 'articlesandbox-articles-section-list' ]			} );

if ( $introContent ) { this.$articlesPage.append(				$intro.append( $introContent ),				introLink.$element			); }

// Events list.on( 'choose', this.onArticleListChoose.bind( this ) );

// Create the list Object.keys( articles ).forEach( function ( page ) {			dialog.populateSection( list, page, articles[ page ]._articles, articles[ page ]._path, 0 );		} ); this.$articlesPage.append( list.$element ); };

/**	 * @inheritdoc */	ArticleDialog.prototype.getBodyHeight = function { // 50% height of window // TODO: Figure out how to make the height variable // every time a page is switched return $( window ).height * 0.5; };

/** Create the interface **/

var mainDialog = new ArticleDialog, windowManager = new OO.ui.WindowManager, mainButton = new OO.ui.ButtonWidget( {			label: mw.msg( 'articlesandbox-button' ),			icon: 'article',			flags: [ 'progressive' ]		} );

mainButton.setDisabled( true ); windowManager.addWindows( [ mainDialog ] );

// Attach events mainButton.on( 'click', function {		windowManager.openWindow( mainDialog );	} );

if ( ARTICLE_REPO ) { // Get the data $.when( buildArticleStructure, getContentFromPage( ARTICLE_REPO ) ) .then(				function ( articles, $introContent ) {					mainDialog.buildContent( articles, $introContent );				},				function ( errorType ) {					mainDialog.setError( errorType );				}			) .always( function {				mainButton.setDisabled( false );			} ); } else { // If ARTICLE_REPO is undefined, show an error mainDialog.setError( 'noconfig' ); mainButton.setDisabled( false ); }

// Load the stylesheet and wait for document ready to attach the // main button to the DOM $.when(		// Stylesheet		mw.loader.load( 'https://www.mediawiki.org/w/index.php?title=User:Mooeypoo/articlesandbox.css&action=raw&ctype=text/css', 'text/css' ),		// Document ready		$.ready	).then( function {		// Attach to DOM		$( '#right-navigation' ).append( mainButton.$element );		$( 'body' ).append( windowManager.$element );	} ); } );