User:Mooeypoo/articlesandbox.js
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.
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 </deep>
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 ) {
// new $.get( '/api/rest_v1/page/summary/' + page )
// .then( function ( result ) {
// console.log( result );
// debugger;
// } );
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': '{{GENDER:|צור|צרי}} ערך',
'articlesandbox-create-article-for': '{{GENDER:|צור|צרי}} ערך עבור $1',
'articlesandbox-sections': 'רשימת ערכים',
'articlesandbox-create-button': '{{GENDER:|צור|צרי}} ערך',
'articlesandbox-create-input-placeholder': 'כותרת הערך',
'articlesandbox-create-articlesummary': 'נוצר באמצעות [https://www.mediawiki.org/wiki/User:Mooeypoo/articlesandbox.js|אשף הערכים]',
'articlesandbox-error-badtitle': 'כותרת הערך שבחרת אינה תקינה. אנא {{GENDER:|נסה|נסי}} שוב.'
},
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 [https://www.mediawiki.org/wiki/User:Mooeypoo/articlesandbox.js|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', 'ext.visualEditor.targetLoader' ] ).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.$intro = $( '<div>' )
.addClass( 'articlesandbox-page-intro-content' )
.append( config.intro );
this.introPanel = new OO.ui.PanelLayout( {
$content: this.$intro,
classes: [ 'articlesandbox-page-intro' ],
framed: true,
expanded: false,
padded: true
} );
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.introPanel.$element,
this.introLink.$element
);
if ( config.$content ) {
this.$element.append( config.$content );
}
this.titleWidget.toggle( config.title );
this.introPanel.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.$intro.empty().append( intro );
this.introLink.setHref( linkToReadMore );
this.introPanel.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' ) :
$( '<span>' )
.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 = $( '<div>' )
.addClass( 'articlesandbox-page-articles' )
this.$createPageForm = $( '<div>' )
.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: $( '<div>' )
.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 $( '<input>' )
.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 = $( '<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 );
};
ArticleDialog.prototype.getRestbasePage = function ( page ) {
return mw.libs.ve.targetLoader.requestParsoidData( page, {} )
.then( function ( response ) {
// content = $( $.parseHTML( response.visualeditor.content ) );
content = response.visualeditor.content;
doc = new DOMParser().parseFromString( content, 'text/html' );
console.log( 'content', $( doc.body.childNodes ) );
return $( doc.body.childNodes );
} );
};
/**
* 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 = this.getRestbasePage( data.path );
} 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 );
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 <div> 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 = $( '<div>' )
.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(
new OO.ui.PanelLayout( {
$content: $introContent,
classes: [ 'articlesandbox-page-intro' ],
framed: true,
expanded: false,
padded: true
} ).$element,
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.7;
};
/** 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 ) )
$.when( buildArticleStructure(), mainDialog.getRestbasePage( 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 );
} );
} );