Differenze tra le versioni di "MediaWiki:Gadget-minipedia.js"
Jump to navigation
Jump to search
m (cosmetic change) |
(fix linear understanding degradation function) |
||
(12 versioni intermedie di uno stesso utente non sono mostrate) | |||
Riga 3: | Riga 3: | ||
* |
* |
||
* Dependencies: mediawiki.util |
* Dependencies: mediawiki.util |
||
+ | * |
||
+ | * See https://phabricator.wikimedia.org/tag/minipedia/ |
||
* |
* |
||
* @revision 2020-06-27 |
* @revision 2020-06-27 |
||
*/ |
*/ |
||
+ | ( function ( $, mw ) { |
||
− | $.when( mw.loader.using( 'mediawiki.util' ), $.ready ).then( function () { |
||
+ | /* |
||
− | // global configuration file |
||
+ | * CONFIGURATION/LOCALIZATION INSTRUCTIONS |
||
+ | * |
||
+ | * Declare somewhere something like this: |
||
+ | * |
||
+ | * // assure that you do not overwrite other-people customizations |
||
+ | * window.MiniPedia = window.MiniPedia || {}; |
||
+ | * window.MiniPedia.L10N = window.MiniPedia.L10N || {}; |
||
+ | * |
||
+ | * // then customize something |
||
+ | * window.MiniPedia.editIntro = 'Project:How to create'; |
||
+ | * window.MiniPedia.L10N.minipedia = 'Otherpedia'; |
||
+ | */ |
||
+ | |||
+ | // load localization defaults |
||
+ | var DEFAULTS = { |
||
+ | // namespace prefix configured in your LocalSettings.php |
||
+ | namespace: 'Mini', |
||
+ | |||
+ | // namespace number configured in your LocalSettings.php |
||
+ | namespaceNum: 3002, |
||
+ | |||
+ | // default edit intro page title |
||
+ | editIntro: 'Progetto:Minipedia/Creazione voce', |
||
+ | |||
+ | // how much characters should have a word to be considered too much lon |
||
+ | // this somehow help people with dyslexia |
||
+ | longWordLen: 13, |
||
+ | |||
+ | // maximum number of acceptable complex words to help people with dyslexia |
||
+ | maxComplexWords: 10, |
||
+ | |||
+ | // min and max number of suggested words to somehow mitigate attention span problems |
||
+ | maxWords: 700, |
||
+ | |||
+ | // maximum number of suggested newlines |
||
+ | maxTotalLines: 180, |
||
+ | |||
+ | // localization stuff |
||
+ | L10N: { |
||
+ | minipedia: "Minipedia", |
||
+ | minipediaShort: "Mini", |
||
+ | normalpedia: "Wikipedia Test", |
||
+ | normalpediaShort: "WikipediaTest", |
||
+ | openMinipedia: "Apri Minipedia", |
||
+ | openNormalpedia: "Apri Wikipedia Test", |
||
+ | createMinipediaPageTitle: "Accesso Minipedia", |
||
+ | createMinipediaPageBody: "Sii il primo a creare una versione più ridotta e più accessibile di questa voce, in Minipedia!", |
||
+ | statsTitle: "Mini Report", |
||
+ | statsHeadingSubject: "Fattore", |
||
+ | statsHeadingValue: "Valore attuale", |
||
+ | statsHeadingExpected: "Limite consigliato", |
||
+ | statsHeadingSimplicity: "Semplicità", |
||
+ | statsWords: "Parole", |
||
+ | statsLines: "Paragrafi", |
||
+ | statsComplexWords: "Parole complesse", |
||
+ | }, |
||
+ | }; |
||
+ | |||
+ | // global configuration |
||
window.MiniPedia = window.MiniPedia || {}; |
window.MiniPedia = window.MiniPedia || {}; |
||
+ | // shortcut |
||
− | // shortcut for the global configuration file (mp = mini-pedia) |
||
var mp = window.MiniPedia; |
var mp = window.MiniPedia; |
||
+ | // inherit default configurations |
||
− | // default namespace name |
||
+ | $.extend( true, mp, DEFAULTS ); |
||
− | mp.namespace = mp.namespace || 'Mini'; |
||
+ | // another shortcut |
||
− | // default edit intro page title |
||
+ | var L10N = mp.L10N; |
||
− | mp.editIntro = mp.editIntro || 'Progetto:Minipedia/Creazione voce'; |
||
+ | /** |
||
− | // work only in the main namespace |
||
+ | * Lazy shortcut to obtain just the first API result |
||
− | var ns = mw.config.get( 'wgNamespaceNumber' ); |
||
+ | * |
||
− | if( ns !== 0 ) { |
||
+ | * @param {Object} response API Response |
||
− | return; |
||
+ | * @return {Object} page object |
||
− | } |
||
+ | */ |
||
+ | function justFirstQueryPage( response ) { |
||
+ | // no response no party |
||
− | // add a "Minipedia" |
||
+ | if( !response.query || !response.query.pages ) { |
||
− | var miniVersionPortletLink = mw.util.addPortletLink( |
||
+ | throw 'no valid API response'; |
||
− | 'p-cactions', |
||
+ | } |
||
− | '#', |
||
− | 'Minipedia', |
||
− | 'ca-minipedia', |
||
− | 'Open Minipedia', |
||
− | 'n' |
||
− | ); |
||
+ | // the list should contain just one page |
||
− | // normal page title and mini version |
||
+ | var pages = response.query.pages; |
||
− | var pageName = mw.config.get( 'wgPageName' ); |
||
+ | for( var id in pages ) { |
||
− | var miniPageName = mp.namespace + ':' + pageName; |
||
+ | return pages[ id ]; |
||
+ | } |
||
+ | |||
+ | // no page no party |
||
+ | return false; |
||
+ | }; |
||
+ | |||
+ | /** |
||
+ | * Get a fresh MediaWiki API object |
||
+ | * |
||
+ | * @return mw.Api |
||
+ | */ |
||
+ | function mwApi() { |
||
+ | return mw.loader.using( 'mediawiki.api' ).then( function() { |
||
+ | return new mw.Api(); |
||
+ | } ); |
||
+ | }; |
||
/** |
/** |
||
* Check if a page title already exists |
* Check if a page title already exists |
||
* |
* |
||
− | * @param title |
+ | * @param title Page title |
+ | * @return Promise |
||
− | * @param callback Callback with one boolean argument telling if the page title exists or not |
||
*/ |
*/ |
||
− | function pageExists( title |
+ | function pageExists( title ) { |
− | // |
+ | // prepare the API request |
+ | var request = { |
||
− | mw.loader.using( 'mediawiki.api' ).then( function() { |
||
+ | action: 'query', |
||
+ | prop: 'info', |
||
+ | titles: title, |
||
+ | }; |
||
− | + | // eventually load API stuff |
|
+ | return mwApi().then( function( api ) { |
||
− | var request = { |
||
− | action: 'query', |
||
− | prop: 'info', |
||
− | titles: title |
||
− | }; |
||
− | // make the API request |
+ | // make the API request |
− | + | return api.get( request ).then( function ( response ) { |
|
− | // |
+ | // check if it exists |
− | + | var page = justFirstQueryPage( response ); |
|
+ | if( page && page.pageid && page.pageid > 0 ) { |
||
− | throw 'no response'; |
||
+ | return page; |
||
} |
} |
||
+ | return false; |
||
− | // the list should contain just one page |
||
− | var page; |
||
− | for( var i in response.query ) { |
||
− | page = response.query[i]; |
||
− | if( page.pageid && page.pageid > 0 ) { |
||
− | |||
− | // exists |
||
− | callback( page ); |
||
− | } |
||
− | } |
||
− | |||
− | // does not exist |
||
− | callback( false ); |
||
} ); |
} ); |
||
− | |||
} ); |
} ); |
||
}; |
}; |
||
/** |
/** |
||
− | * |
+ | * Query the current page plain text |
* |
* |
||
− | * @param |
+ | * @param {String} Page name (or none for the current one) |
+ | * @return Promise |
||
*/ |
*/ |
||
− | function |
+ | function queryPlainText( pageName ) { |
+ | // complete page title with namespace |
||
− | // eventually just inherit this page name |
||
+ | pageName = pageName || mw.config.get( 'wgPageName' ); |
||
− | title = title || pageName; |
||
+ | // prepare the API request |
||
− | var mpTitle = mp.namespace + ':' + title; |
||
+ | // See https://phabricator.wikimedia.org/T259332 |
||
− | |||
− | var |
+ | var request = { |
− | action: ' |
+ | action: 'query', |
− | + | prop: 'extracts', |
|
− | + | titles: pageName, |
|
+ | explaintext: 1, |
||
− | editintro: mp.editIntro, |
||
+ | exlimit: 1, |
||
+ | exsectionformat: 'plain', |
||
}; |
}; |
||
+ | // this will return a Promise resolving the page plain text, returned upstream |
||
− | // '/index.php' |
||
+ | return mwApi().then( function( api ) { |
||
− | var wgScript = mw.config.get( 'wgScript' ); |
||
− | // |
+ | // make the API request and return a Promise |
+ | return api.get( request ).then( function( response ) { |
||
− | window.location = wgScript + '?' + $.param( queryString ); |
||
+ | |||
+ | // resolve the Promise with the page content |
||
+ | var page = justFirstQueryPage( response ); |
||
+ | if( page && page.pageid && page.pageid > 0 && page.extract ) { |
||
+ | return page.extract; |
||
+ | } |
||
+ | |||
+ | // no content |
||
+ | return false; |
||
+ | |||
+ | } ); |
||
+ | } ); |
||
}; |
}; |
||
/** |
/** |
||
− | * |
+ | * Prepare the wiki |
− | * |
||
− | * @param title Page title without Minipedia namespace |
||
*/ |
*/ |
||
− | function |
+ | function prepareNormalpedia() { |
+ | |||
+ | // normal page title and mini version |
||
+ | var pageName = mw.config.get( 'wgPageName' ); |
||
+ | // prepare the Minipedia title object |
||
− | // eventually just inherit this page name |
||
− | + | var miniTitleObject = new mw.Title( pageName, mp.namespaceNum ); |
|
+ | // minipedia page title with prefix |
||
− | var miniTitleObject = new mw.Title( title, mp.namespace ); |
||
+ | var miniPageName = miniTitleObject.getPrefixedText(); |
||
− | // |
+ | // minipedia page URL |
− | + | var miniPageUrl = miniTitleObject.getUrl(); |
|
− | }; |
||
+ | /** |
||
− | // on the mini toolback click, check if a mini version exists |
||
+ | * Go to the Minipedia page in edit mode |
||
− | $( miniVersionPortletLink ).click( function() { |
||
+ | * |
||
+ | * The page in the main namespace will be preloaded. |
||
+ | |||
+ | */ |
||
+ | function goToMinipediaEditPage() { |
||
+ | /** |
||
− | // check if the mini version exists |
||
+ | * Build the query string to edit a page |
||
− | pageExists( miniPageName, function( miniPageExists ) { |
||
+ | * |
||
+ | * TODO: eventually add VisualEditor support |
||
+ | */ |
||
+ | var editPageQueryString = { |
||
+ | action: 'edit', |
||
+ | title: miniPageName, |
||
+ | preload: pageName, |
||
+ | editintro: mp.editIntro, |
||
+ | }; |
||
+ | |||
+ | // '/index.php' |
||
+ | var wgScript = mw.config.get( 'wgScript' ); |
||
+ | |||
+ | // go to the edit page URL |
||
+ | window.location = wgScript + '?' + $.param( editPageQueryString ); |
||
+ | }; |
||
+ | |||
+ | // add a "Minipedia" |
||
+ | var miniVersionPortletLink = mw.util.addPortletLink( |
||
+ | 'p-namespaces', |
||
+ | miniPageUrl, |
||
+ | L10N.minipediaShort, |
||
+ | 'ca-minipedia', |
||
+ | L10N.openMinipedia, |
||
+ | 'n' |
||
+ | ); |
||
+ | |||
+ | // on the mini toolback click, check if a mini version exists |
||
+ | $( miniVersionPortletLink ).click( function( e ) { |
||
+ | |||
+ | // wait for multiple information |
||
+ | $.when( |
||
+ | // check if the page really exists |
||
+ | pageExists( miniPageName ), |
||
+ | |||
+ | // allow to open OO UI windows |
||
+ | mw.loader.using( 'oojs-ui-windows' ) |
||
− | // |
+ | // callback fired when we have all the information |
− | + | ).done( function( miniPageExists, loader ) { |
|
// check if the page already exist |
// check if the page already exist |
||
Riga 139: | Riga 260: | ||
// just redirect to the Minipedia version |
// just redirect to the Minipedia version |
||
+ | window.location = miniPageUrl; |
||
− | goToMinipediaPageTitle(); |
||
} else { |
} else { |
||
Riga 152: | Riga 273: | ||
// configure and open dialog |
// configure and open dialog |
||
var windowInstance = windowManager.openWindow( messageDialog, { |
var windowInstance = windowManager.openWindow( messageDialog, { |
||
− | title: |
+ | title: L10N.createMinipediaPageTitle, |
+ | message: L10N.createMinipediaPageBody, |
||
− | message: "Sii il primo a creare una versione più ridotta e più accessibile di questa voce, in Minipedia!" |
||
} ); |
} ); |
||
// check if you accepted the page creation |
// check if you accepted the page creation |
||
windowInstance.closed.then( function ( data ) { |
windowInstance.closed.then( function ( data ) { |
||
+ | |||
+ | // user is confirming the action |
||
if( data.action === 'accept' ) { |
if( data.action === 'accept' ) { |
||
− | // go go go! |
+ | // go go go! to mini |
− | + | goToMinipediaEditPage(); |
|
} |
} |
||
} ); |
} ); |
||
+ | |||
} |
} |
||
+ | // end if page exists |
||
} ); |
} ); |
||
+ | // end $.when() |
||
+ | |||
+ | // avoid scrolling to the top |
||
+ | e.preventDefault(); |
||
+ | |||
} ); |
} ); |
||
+ | // end $( miniVersionPortletLink ).click |
||
− | } ); |
||
+ | |||
− | } ); |
||
+ | }; |
||
+ | // end prepareNormalWiki() |
||
+ | |||
+ | /** |
||
+ | * Calculate some stats over a text |
||
+ | */ |
||
+ | function textStats( text ) { |
||
+ | |||
+ | var stats = {}; |
||
+ | |||
+ | text = text.trim(); |
||
+ | |||
+ | // count long words |
||
+ | var totalLongWords = 0; |
||
+ | var word, words = text.split( /\s+/ ); |
||
+ | for( var i = 0; i < words.length; i++ ) { |
||
+ | word = words[i]; |
||
+ | if( word.length > mp.longWordLen ) { |
||
+ | totalLongWords++; |
||
+ | } |
||
+ | } |
||
+ | |||
+ | // total amount of paragraphs |
||
+ | // paragraphs shorter than this comment are discarded |
||
+ | var totalLines = 0; |
||
+ | var paragraphs = text.split( /\n+/ ); |
||
+ | var paragraph; |
||
+ | for( var i = 0; i < paragraphs.length; i++ ) { |
||
+ | paragraph = paragraphs[i].trim(); |
||
+ | if( paragraph.length > 30 ) { |
||
+ | totalLines++; |
||
+ | } |
||
+ | } |
||
+ | |||
+ | // how much lines? |
||
+ | stats.totalLines = totalLines; |
||
+ | |||
+ | // how much words? |
||
+ | stats.totalWords = words.length; |
||
+ | |||
+ | // how much of these words are so much long? |
||
+ | // See 'longWordLen' |
||
+ | stats.totalLongWords = totalLongWords; |
||
+ | |||
+ | return stats; |
||
+ | }; |
||
+ | |||
+ | /** |
||
+ | * Query content stats of the current page |
||
+ | * |
||
+ | * @param {String} Page title (or nothing for the current one) |
||
+ | * @return Promise |
||
+ | */ |
||
+ | function queryContentStats( pageTitle ) { |
||
+ | return queryPlainText( pageTitle ).then( textStats ); |
||
+ | }; |
||
+ | |||
+ | |||
+ | /** |
||
+ | * Apply a stupid percentage |
||
+ | * |
||
+ | * @param {int} a |
||
+ | * @param {int} b |
||
+ | * @return The 'b%' applied to 'a' |
||
+ | */ |
||
+ | function applyPercentage( a, b ) { |
||
+ | return parseInt( a * b / 100 ); |
||
+ | }; |
||
+ | |||
+ | /** |
||
+ | * Prepare the Minipedia stats box |
||
+ | */ |
||
+ | function prepareMinipediaStatsBox() { |
||
+ | |||
+ | // normal page title (non-mini version) |
||
+ | var mainPageTitle = mw.config.get( 'wgTitle' ); |
||
+ | |||
+ | // body container |
||
+ | var $contentText = $( '#mw-content-text' ); |
||
+ | |||
+ | // prepare the DOM tree |
||
+ | var $container = $( '<div>' ); |
||
+ | var $table = $( '<table>' ); |
||
+ | var $thead = $( '<thead>' ); |
||
+ | var $tbody = $( '<tbody>' ); |
||
+ | |||
+ | // prepare the stats container |
||
+ | $container.addClass( 'minipedia-stats' ); |
||
+ | |||
+ | // put a title |
||
+ | $container.append( $( '<h2>' ).text( L10N.statsTitle ) ); |
||
+ | |||
+ | // put the table |
||
+ | $container.append( $table ); |
||
+ | |||
+ | // add table headers |
||
+ | $thead.append( |
||
+ | $( '<tr>' ).append( $( '<th>' ).text( L10N.statsHeadingSubject ) ) |
||
+ | .append( $( '<th>' ).text( L10N.statsHeadingValue ) ) |
||
+ | .append( $( '<th>' ).text( L10N.statsHeadingExpected ) ) |
||
+ | .append( $( '<th>' ).text( L10N.statsHeadingSimplicity ) ) |
||
+ | ); |
||
+ | |||
+ | // prepare the table |
||
+ | $table.addClass( 'wikitable' ) |
||
+ | .append( $thead ) |
||
+ | .append( $tbody ); |
||
+ | |||
+ | /** |
||
+ | * Append a row (with a label and a value) into a table |
||
+ | * |
||
+ | * The data argument accepts an object with: |
||
+ | * |
||
+ | * className: Class name for the row |
||
+ | * label: Text displayed in row heading (left) |
||
+ | * text: Text displayed in row data (right) |
||
+ | * value Value associated to the text of the mini version |
||
+ | * maxValue Maximum suggested value |
||
+ | * upstreamValue The value of the upstream (main) version |
||
+ | * upstreamLimitPerc The percentage (0-100) applied to the 'upstreamValue' to inherit a suitable 'maxValue' |
||
+ | * |
||
+ | * Note: this function uses OOUI widgets. Make sure to have them loaded. |
||
+ | * |
||
+ | * @param {Object} jQuery table |
||
+ | * @param {Object} Data information |
||
+ | */ |
||
+ | function appendTableStatsRow( $table, data ) { |
||
+ | |||
+ | // read arguments |
||
+ | var className = data.className; |
||
+ | var label = data.label; |
||
+ | var value = data.value; |
||
+ | var maxValue = data.maxValue; |
||
+ | var text = data.text || value; |
||
+ | |||
+ | // eventually calculate the max value |
||
+ | if( !maxValue && data.upstreamValue && data.upstreamLimitPerc ) { |
||
+ | maxValue = applyPercentage( data.upstreamValue, data.upstreamLimitPerc ); |
||
+ | } |
||
+ | |||
+ | // prepare table stats row |
||
+ | var $tr = $( '<tr>' ); |
||
+ | var $tdLabel = $( '<td>' ).text( label ); |
||
+ | var $tdValue = $( '<td>' ).text( text ); |
||
+ | var $tdMax = $( '<td>' ).text( maxValue ); |
||
+ | var $tdProgress = $( '<td>' ); |
||
+ | |||
+ | // build the table row |
||
+ | $tr.addClass( 'minipedia-stats-row-' + className ); |
||
+ | $tr.append( $tdLabel ) |
||
+ | .append( $tdValue ) |
||
+ | .append( $tdMax ) |
||
+ | .append( $tdProgress ); |
||
+ | |||
+ | // if possible, plot a cute progress bar |
||
+ | if( maxValue ) { |
||
+ | |||
+ | // calculate a 0-100 progress since the value and maxValue |
||
+ | var realPercentage = parseInt( value / maxValue * 100 ); |
||
+ | |||
+ | /** |
||
+ | * Calculate the inverse percentage |
||
+ | * |
||
+ | * This is the final percentage shown to the user. |
||
+ | * |
||
+ | * In short, it's always 100% but if you go over |
||
+ | * the limit it start decreasing and reaching zero, |
||
+ | * to rappresent a kind of 'understanding degradation'. |
||
+ | * |
||
+ | * To do not underrate the work of the user it should not go |
||
+ | * below a certain minimum amount. Example: 10%. :^) |
||
+ | * |
||
+ | * Actually the function is just linear. |
||
+ | */ |
||
+ | var inversePercentage = 100; |
||
+ | if( realPercentage > 100 ) { |
||
+ | inversePercentage = Math.max( 10, 200 - realPercentage ); |
||
+ | } |
||
+ | |||
+ | // generate the progress bar |
||
+ | var progressBar = new OO.ui.ProgressBarWidget( { |
||
+ | progress: inversePercentage, |
||
+ | } ); |
||
+ | |||
+ | // show the progress bar |
||
+ | $tdProgress.append( progressBar.$element ); |
||
+ | } |
||
+ | |||
+ | // attach some data to be read by scripts |
||
+ | $tr.data( 'ministats', data ); |
||
+ | |||
+ | // eventually emphasize if something is wrong |
||
+ | if( value && maxValue && value > maxValue ) { |
||
+ | $tr.addClass( 'minipedia-stats-row-problem' ); |
||
+ | } |
||
+ | |||
+ | // show the row in the stats table |
||
+ | $table.append( $tr ); |
||
+ | }; |
||
+ | |||
+ | // request multiple stuff at the same time |
||
+ | $.when( |
||
+ | |||
+ | // query the page plain text of the current mini page |
||
+ | queryContentStats(), |
||
+ | |||
+ | // query the page plain text of the related non-mini page |
||
+ | queryContentStats( mainPageTitle ), |
||
+ | |||
+ | // require the progress bar widget |
||
+ | mw.loader.using( 'oojs-ui-widgets' ) |
||
+ | |||
+ | // callback fired when we have all the information |
||
+ | ).done( function( statsMini, statsMain, mwLoader ) { |
||
+ | |||
+ | // show the stats container at the bottom of the page when we have something |
||
+ | $contentText.append( $container ); |
||
+ | |||
+ | // check if we have also some information from the main namespace |
||
+ | // in this case we can do a comparison |
||
+ | if( statsMain ) { |
||
+ | |||
+ | // show the number of words, lines and complex words (actually just long words) |
||
+ | // they can be compared to the main version |
||
+ | appendTableStatsRow( $table, { |
||
+ | className: 'long-words', |
||
+ | label: L10N.statsComplexWords, |
||
+ | value: statsMini.totalLongWords, |
||
+ | upstreamValue: statsMain.totalLongWords, |
||
+ | upstreamLimitPerc: 10, |
||
+ | } ); |
||
+ | appendTableStatsRow( $table, { |
||
+ | className: 'words', |
||
+ | label: L10N.statsWords, |
||
+ | value: statsMini.totalWords, |
||
+ | upstreamValue: statsMain.totalWords, |
||
+ | upstreamLimitPerc: 30, |
||
+ | } ); |
||
+ | appendTableStatsRow( $table, { |
||
+ | className: 'lines', |
||
+ | label: L10N.statsLines, |
||
+ | value: statsMini.totalLines, |
||
+ | maxValue: statsMain.totalLines, |
||
+ | upstreamLimitPerc: 30, |
||
+ | } ); |
||
+ | |||
+ | // there is no the related main page |
||
+ | } else { |
||
+ | |||
+ | // show the number of words, lines and complex words (actually just long words) |
||
+ | // they cannot be compared to the main version |
||
+ | appendTableStatsRow( $table, { |
||
+ | className: 'long-words', |
||
+ | label: L10N.statsComplexWords, |
||
+ | value: statsMini.totalLongWords, |
||
+ | maxValue: mp.maxComplexWords, |
||
+ | } ); |
||
+ | appendTableStatsRow( $table, { |
||
+ | className: 'words', |
||
+ | label: L10N.statsWords, |
||
+ | value: statsMini.totalWords, |
||
+ | maxValue: mp.maxStatsWords, |
||
+ | } ); |
||
+ | appendTableStatsRow( $table, { |
||
+ | className: 'lines', |
||
+ | label: L10N.statsLines, |
||
+ | value: statsMini.totalLines, |
||
+ | maxValue: mp.maxTotalLines, |
||
+ | } ); |
||
+ | } |
||
+ | } ); |
||
+ | }; |
||
+ | |||
+ | /** |
||
+ | * Prepare the Minipedia namespace |
||
+ | */ |
||
+ | function prepareMinipedia() { |
||
+ | |||
+ | // action of the page (edit, view etc.) |
||
+ | var action = mw.config.get( 'wgAction' ); |
||
+ | |||
+ | // normal page title and mini version |
||
+ | var pageName = mw.config.get( 'wgTitle' ); |
||
+ | |||
+ | // prepare the Normalpedia title object |
||
+ | var normalTitleObject = new mw.Title( pageName ); |
||
+ | |||
+ | // minipedia page title with prefix |
||
+ | var normalPageName = normalTitleObject.getPrefixedText(); |
||
+ | |||
+ | // minipedia page URL |
||
+ | var normalPageUrl = normalTitleObject.getUrl(); |
||
+ | |||
+ | // add a "Minipedia" |
||
+ | var normalVersionPortletLink = mw.util.addPortletLink( |
||
+ | 'p-namespaces', |
||
+ | normalPageUrl, |
||
+ | L10N.normalpediaShort, |
||
+ | 'ca-normalpedia', |
||
+ | L10N.openNormalpedia |
||
+ | ); |
||
+ | |||
+ | // check if we are in view mode |
||
+ | if( action === 'view' ) { |
||
+ | |||
+ | // in view mode we can fetch the stats |
||
+ | prepareMinipediaStatsBox(); |
||
+ | } |
||
+ | }; |
||
+ | // end prepareMinipedia() |
||
+ | |||
+ | // work only in the main namespace |
||
+ | var ns = mw.config.get( 'wgNamespaceNumber' ); |
||
+ | if( ns === 0 ) { |
||
+ | prepareNormalpedia(); |
||
+ | } else if( ns == mp.namespaceNum ) { |
||
+ | prepareMinipedia(); |
||
+ | } |
||
+ | // end namespace zero check |
||
+ | |||
+ | } )( $, mw ); |
Versione attuale delle 15:54, 14 ago 2020
/** * Make Minipedia magics * * Dependencies: mediawiki.util * * See https://phabricator.wikimedia.org/tag/minipedia/ * * @revision 2020-06-27 */ ( function ( $, mw ) { /* * CONFIGURATION/LOCALIZATION INSTRUCTIONS * * Declare somewhere something like this: * * // assure that you do not overwrite other-people customizations * window.MiniPedia = window.MiniPedia || {}; * window.MiniPedia.L10N = window.MiniPedia.L10N || {}; * * // then customize something * window.MiniPedia.editIntro = 'Project:How to create'; * window.MiniPedia.L10N.minipedia = 'Otherpedia'; */ // load localization defaults var DEFAULTS = { // namespace prefix configured in your LocalSettings.php namespace: 'Mini', // namespace number configured in your LocalSettings.php namespaceNum: 3002, // default edit intro page title editIntro: 'Progetto:Minipedia/Creazione voce', // how much characters should have a word to be considered too much lon // this somehow help people with dyslexia longWordLen: 13, // maximum number of acceptable complex words to help people with dyslexia maxComplexWords: 10, // min and max number of suggested words to somehow mitigate attention span problems maxWords: 700, // maximum number of suggested newlines maxTotalLines: 180, // localization stuff L10N: { minipedia: "Minipedia", minipediaShort: "Mini", normalpedia: "Wikipedia Test", normalpediaShort: "WikipediaTest", openMinipedia: "Apri Minipedia", openNormalpedia: "Apri Wikipedia Test", createMinipediaPageTitle: "Accesso Minipedia", createMinipediaPageBody: "Sii il primo a creare una versione più ridotta e più accessibile di questa voce, in Minipedia!", statsTitle: "Mini Report", statsHeadingSubject: "Fattore", statsHeadingValue: "Valore attuale", statsHeadingExpected: "Limite consigliato", statsHeadingSimplicity: "Semplicità", statsWords: "Parole", statsLines: "Paragrafi", statsComplexWords: "Parole complesse", }, }; // global configuration window.MiniPedia = window.MiniPedia || {}; // shortcut var mp = window.MiniPedia; // inherit default configurations $.extend( true, mp, DEFAULTS ); // another shortcut var L10N = mp.L10N; /** * Lazy shortcut to obtain just the first API result * * @param {Object} response API Response * @return {Object} page object */ function justFirstQueryPage( response ) { // no response no party if( !response.query || !response.query.pages ) { throw 'no valid API response'; } // the list should contain just one page var pages = response.query.pages; for( var id in pages ) { return pages[ id ]; } // no page no party return false; }; /** * Get a fresh MediaWiki API object * * @return mw.Api */ function mwApi() { return mw.loader.using( 'mediawiki.api' ).then( function() { return new mw.Api(); } ); }; /** * Check if a page title already exists * * @param title Page title * @return Promise */ function pageExists( title ) { // prepare the API request var request = { action: 'query', prop: 'info', titles: title, }; // eventually load API stuff return mwApi().then( function( api ) { // make the API request return api.get( request ).then( function ( response ) { // check if it exists var page = justFirstQueryPage( response ); if( page && page.pageid && page.pageid > 0 ) { return page; } return false; } ); } ); }; /** * Query the current page plain text * * @param {String} Page name (or none for the current one) * @return Promise */ function queryPlainText( pageName ) { // complete page title with namespace pageName = pageName || mw.config.get( 'wgPageName' ); // prepare the API request // See https://phabricator.wikimedia.org/T259332 var request = { action: 'query', prop: 'extracts', titles: pageName, explaintext: 1, exlimit: 1, exsectionformat: 'plain', }; // this will return a Promise resolving the page plain text, returned upstream return mwApi().then( function( api ) { // make the API request and return a Promise return api.get( request ).then( function( response ) { // resolve the Promise with the page content var page = justFirstQueryPage( response ); if( page && page.pageid && page.pageid > 0 && page.extract ) { return page.extract; } // no content return false; } ); } ); }; /** * Prepare the wiki */ function prepareNormalpedia() { // normal page title and mini version var pageName = mw.config.get( 'wgPageName' ); // prepare the Minipedia title object var miniTitleObject = new mw.Title( pageName, mp.namespaceNum ); // minipedia page title with prefix var miniPageName = miniTitleObject.getPrefixedText(); // minipedia page URL var miniPageUrl = miniTitleObject.getUrl(); /** * Go to the Minipedia page in edit mode * * The page in the main namespace will be preloaded. */ function goToMinipediaEditPage() { /** * Build the query string to edit a page * * TODO: eventually add VisualEditor support */ var editPageQueryString = { action: 'edit', title: miniPageName, preload: pageName, editintro: mp.editIntro, }; // '/index.php' var wgScript = mw.config.get( 'wgScript' ); // go to the edit page URL window.location = wgScript + '?' + $.param( editPageQueryString ); }; // add a "Minipedia" var miniVersionPortletLink = mw.util.addPortletLink( 'p-namespaces', miniPageUrl, L10N.minipediaShort, 'ca-minipedia', L10N.openMinipedia, 'n' ); // on the mini toolback click, check if a mini version exists $( miniVersionPortletLink ).click( function( e ) { // wait for multiple information $.when( // check if the page really exists pageExists( miniPageName ), // allow to open OO UI windows mw.loader.using( 'oojs-ui-windows' ) // callback fired when we have all the information ).done( function( miniPageExists, loader ) { // check if the page already exist if( miniPageExists ) { // just redirect to the Minipedia version window.location = miniPageUrl; } else { // ask if you want to create the page // create message dialog window var messageDialog = new OO.ui.MessageDialog(); var windowManager = new OO.ui.WindowManager(); $( 'body' ).append( windowManager.$element ); windowManager.addWindows( [ messageDialog ] ); // configure and open dialog var windowInstance = windowManager.openWindow( messageDialog, { title: L10N.createMinipediaPageTitle, message: L10N.createMinipediaPageBody, } ); // check if you accepted the page creation windowInstance.closed.then( function ( data ) { // user is confirming the action if( data.action === 'accept' ) { // go go go! to mini goToMinipediaEditPage(); } } ); } // end if page exists } ); // end $.when() // avoid scrolling to the top e.preventDefault(); } ); // end $( miniVersionPortletLink ).click }; // end prepareNormalWiki() /** * Calculate some stats over a text */ function textStats( text ) { var stats = {}; text = text.trim(); // count long words var totalLongWords = 0; var word, words = text.split( /\s+/ ); for( var i = 0; i < words.length; i++ ) { word = words[i]; if( word.length > mp.longWordLen ) { totalLongWords++; } } // total amount of paragraphs // paragraphs shorter than this comment are discarded var totalLines = 0; var paragraphs = text.split( /\n+/ ); var paragraph; for( var i = 0; i < paragraphs.length; i++ ) { paragraph = paragraphs[i].trim(); if( paragraph.length > 30 ) { totalLines++; } } // how much lines? stats.totalLines = totalLines; // how much words? stats.totalWords = words.length; // how much of these words are so much long? // See 'longWordLen' stats.totalLongWords = totalLongWords; return stats; }; /** * Query content stats of the current page * * @param {String} Page title (or nothing for the current one) * @return Promise */ function queryContentStats( pageTitle ) { return queryPlainText( pageTitle ).then( textStats ); }; /** * Apply a stupid percentage * * @param {int} a * @param {int} b * @return The 'b%' applied to 'a' */ function applyPercentage( a, b ) { return parseInt( a * b / 100 ); }; /** * Prepare the Minipedia stats box */ function prepareMinipediaStatsBox() { // normal page title (non-mini version) var mainPageTitle = mw.config.get( 'wgTitle' ); // body container var $contentText = $( '#mw-content-text' ); // prepare the DOM tree var $container = $( '<div>' ); var $table = $( '<table>' ); var $thead = $( '<thead>' ); var $tbody = $( '<tbody>' ); // prepare the stats container $container.addClass( 'minipedia-stats' ); // put a title $container.append( $( '<h2>' ).text( L10N.statsTitle ) ); // put the table $container.append( $table ); // add table headers $thead.append( $( '<tr>' ).append( $( '<th>' ).text( L10N.statsHeadingSubject ) ) .append( $( '<th>' ).text( L10N.statsHeadingValue ) ) .append( $( '<th>' ).text( L10N.statsHeadingExpected ) ) .append( $( '<th>' ).text( L10N.statsHeadingSimplicity ) ) ); // prepare the table $table.addClass( 'wikitable' ) .append( $thead ) .append( $tbody ); /** * Append a row (with a label and a value) into a table * * The data argument accepts an object with: * * className: Class name for the row * label: Text displayed in row heading (left) * text: Text displayed in row data (right) * value Value associated to the text of the mini version * maxValue Maximum suggested value * upstreamValue The value of the upstream (main) version * upstreamLimitPerc The percentage (0-100) applied to the 'upstreamValue' to inherit a suitable 'maxValue' * * Note: this function uses OOUI widgets. Make sure to have them loaded. * * @param {Object} jQuery table * @param {Object} Data information */ function appendTableStatsRow( $table, data ) { // read arguments var className = data.className; var label = data.label; var value = data.value; var maxValue = data.maxValue; var text = data.text || value; // eventually calculate the max value if( !maxValue && data.upstreamValue && data.upstreamLimitPerc ) { maxValue = applyPercentage( data.upstreamValue, data.upstreamLimitPerc ); } // prepare table stats row var $tr = $( '<tr>' ); var $tdLabel = $( '<td>' ).text( label ); var $tdValue = $( '<td>' ).text( text ); var $tdMax = $( '<td>' ).text( maxValue ); var $tdProgress = $( '<td>' ); // build the table row $tr.addClass( 'minipedia-stats-row-' + className ); $tr.append( $tdLabel ) .append( $tdValue ) .append( $tdMax ) .append( $tdProgress ); // if possible, plot a cute progress bar if( maxValue ) { // calculate a 0-100 progress since the value and maxValue var realPercentage = parseInt( value / maxValue * 100 ); /** * Calculate the inverse percentage * * This is the final percentage shown to the user. * * In short, it's always 100% but if you go over * the limit it start decreasing and reaching zero, * to rappresent a kind of 'understanding degradation'. * * To do not underrate the work of the user it should not go * below a certain minimum amount. Example: 10%. :^) * * Actually the function is just linear. */ var inversePercentage = 100; if( realPercentage > 100 ) { inversePercentage = Math.max( 10, 200 - realPercentage ); } // generate the progress bar var progressBar = new OO.ui.ProgressBarWidget( { progress: inversePercentage, } ); // show the progress bar $tdProgress.append( progressBar.$element ); } // attach some data to be read by scripts $tr.data( 'ministats', data ); // eventually emphasize if something is wrong if( value && maxValue && value > maxValue ) { $tr.addClass( 'minipedia-stats-row-problem' ); } // show the row in the stats table $table.append( $tr ); }; // request multiple stuff at the same time $.when( // query the page plain text of the current mini page queryContentStats(), // query the page plain text of the related non-mini page queryContentStats( mainPageTitle ), // require the progress bar widget mw.loader.using( 'oojs-ui-widgets' ) // callback fired when we have all the information ).done( function( statsMini, statsMain, mwLoader ) { // show the stats container at the bottom of the page when we have something $contentText.append( $container ); // check if we have also some information from the main namespace // in this case we can do a comparison if( statsMain ) { // show the number of words, lines and complex words (actually just long words) // they can be compared to the main version appendTableStatsRow( $table, { className: 'long-words', label: L10N.statsComplexWords, value: statsMini.totalLongWords, upstreamValue: statsMain.totalLongWords, upstreamLimitPerc: 10, } ); appendTableStatsRow( $table, { className: 'words', label: L10N.statsWords, value: statsMini.totalWords, upstreamValue: statsMain.totalWords, upstreamLimitPerc: 30, } ); appendTableStatsRow( $table, { className: 'lines', label: L10N.statsLines, value: statsMini.totalLines, maxValue: statsMain.totalLines, upstreamLimitPerc: 30, } ); // there is no the related main page } else { // show the number of words, lines and complex words (actually just long words) // they cannot be compared to the main version appendTableStatsRow( $table, { className: 'long-words', label: L10N.statsComplexWords, value: statsMini.totalLongWords, maxValue: mp.maxComplexWords, } ); appendTableStatsRow( $table, { className: 'words', label: L10N.statsWords, value: statsMini.totalWords, maxValue: mp.maxStatsWords, } ); appendTableStatsRow( $table, { className: 'lines', label: L10N.statsLines, value: statsMini.totalLines, maxValue: mp.maxTotalLines, } ); } } ); }; /** * Prepare the Minipedia namespace */ function prepareMinipedia() { // action of the page (edit, view etc.) var action = mw.config.get( 'wgAction' ); // normal page title and mini version var pageName = mw.config.get( 'wgTitle' ); // prepare the Normalpedia title object var normalTitleObject = new mw.Title( pageName ); // minipedia page title with prefix var normalPageName = normalTitleObject.getPrefixedText(); // minipedia page URL var normalPageUrl = normalTitleObject.getUrl(); // add a "Minipedia" var normalVersionPortletLink = mw.util.addPortletLink( 'p-namespaces', normalPageUrl, L10N.normalpediaShort, 'ca-normalpedia', L10N.openNormalpedia ); // check if we are in view mode if( action === 'view' ) { // in view mode we can fetch the stats prepareMinipediaStatsBox(); } }; // end prepareMinipedia() // work only in the main namespace var ns = mw.config.get( 'wgNamespaceNumber' ); if( ns === 0 ) { prepareNormalpedia(); } else if( ns == mp.namespaceNum ) { prepareMinipedia(); } // end namespace zero check } )( $, mw );