diff --git a/extension.json b/extension.json index 1e8ac646d..d1cbca13e 100644 --- a/extension.json +++ b/extension.json @@ -55,8 +55,8 @@ "PopupsOptInDefaultState": "0", "@PopupsConflictingNavPopupsGadgetName": "@var string: Navigation popups gadget name", "PopupsConflictingNavPopupsGadgetName": "Navigation_popups", - "@PopupsAPIUseRESTBase": "Whether to use RESTBase rather than the MediaWiki API for fetching Popups data.", - "PopupsAPIUseRESTBase": false, + "@PopupsGateway": "Which gateway to use for fetching Popups data. Available options: [mwApiPlain|restbasePlain|restbaseHTML]. Full and always up to date list is available in src/gateway/index.js", + "PopupsGateway": "mwApiPlain", "@PopupsAnonsEnabledSamplingRate": "Sampling rate for showing popups to anonymous users.", "PopupsAnonsEnabledSamplingRate": 0.9, "@PopupsStatsvSamplingRate": "Sampling rate for logging performance data to statsv.", diff --git a/includes/PopupsHooks.php b/includes/PopupsHooks.php index f7b7d001c..d53e0ef18 100644 --- a/includes/PopupsHooks.php +++ b/includes/PopupsHooks.php @@ -120,7 +120,7 @@ class PopupsHooks { $conf = PopupsContext::getInstance()->getConfig(); $vars['wgPopupsSchemaSamplingRate'] = $conf->get( 'PopupsSchemaSamplingRate' ); $vars['wgPopupsBetaFeature'] = $conf->get( 'PopupsBetaFeature' ); - $vars['wgPopupsAPIUseRESTBase'] = $conf->get( 'PopupsAPIUseRESTBase' ); + $vars['wgPopupsGateway'] = $conf->get( 'PopupsGateway' ); $vars['wgPopupsAnonsEnabledSamplingRate'] = $conf->get( 'PopupsAnonsEnabledSamplingRate' ); $vars['wgPopupsStatsvSamplingRate'] = $conf->get( 'PopupsStatsvSamplingRate' ); } diff --git a/resources/dist/index.js b/resources/dist/index.js index e69f7f22f..53007caa9 100644 Binary files a/resources/dist/index.js and b/resources/dist/index.js differ diff --git a/resources/dist/index.js.map b/resources/dist/index.js.map index 06b9f6a36..39a0fdfc6 100644 Binary files a/resources/dist/index.js.map and b/resources/dist/index.js.map differ diff --git a/src/formatter.js b/src/formatter.js index 096f547e4..aabfef275 100644 --- a/src/formatter.js +++ b/src/formatter.js @@ -7,14 +7,14 @@ var $ = jQuery, * @param {String} title * @returns {Array} */ -function htmlize( plainTextExtract, title ) { +function formatPlainTextExtract( plainTextExtract, title ) { var extract = plainTextExtract; if ( plainTextExtract === undefined ) { return []; } extract = removeParentheticals( extract ); - extract = removeEllipsis( extract ); + extract = removeTrailingEllipsis( extract ); // After cleaning the extract it may have been blanked if ( extract.length === 0 ) { @@ -78,7 +78,7 @@ function makeTitleInExtractBold( extract, title ) { * @param {String} extract * @return {String} */ -function removeEllipsis( extract ) { +function removeTrailingEllipsis( extract ) { return extract.replace( /\.\.\.$/, '' ); } @@ -127,5 +127,7 @@ function removeParentheticals( extract ) { } module.exports = { - htmlize: htmlize + formatPlainTextExtract: formatPlainTextExtract, + removeTrailingEllipsis: removeTrailingEllipsis, + removeParentheticals: removeParentheticals }; diff --git a/src/gateway/html/rest.js b/src/gateway/html/rest.js new file mode 100644 index 000000000..7e07b5309 --- /dev/null +++ b/src/gateway/html/rest.js @@ -0,0 +1,35 @@ +var formatter = require( '../../formatter' ), + restbaseProvider = require( '../restProvider' ), + $ = jQuery; + +/** + * Creates an instance of the RESTBase gateway. + * + * This gateway differs from the {@link MediaWikiGateway MediaWiki gateway} in + * that it fetches page data from [the RESTBase page summary endpoint][0]. + * + * [0]: https://en.wikipedia.org/api/rest_v1/#!/Page_content/get_page_summary_title + * + * @param {Function} ajax A function with the same signature as `jQuery.ajax` + * @param {Object} config Configuration that affects the major behavior of the + * gateway. + * @param {Number} config.THUMBNAIL_SIZE The length of the major dimension of + * the thumbnail. + * @returns {RESTBaseGateway} + */ +module.exports = function createRESTHTMLBaseGateway( ajax, config ) { + return restbaseProvider( ajax, config, parseHTMLResponse ); +}; + +/** + * Prepare extract + * @param {Object} page Rest response + * @returns {Array} An array of DOM Elements + */ +function parseHTMLResponse( page ) { + var extract = page.extract_html; + extract = formatter.removeTrailingEllipsis( extract ); + extract = formatter.removeParentheticals( extract ); + + return extract.length === 0 ? [] : $.parseHTML( extract ); +} diff --git a/src/gateway/index.js b/src/gateway/index.js index 6baa6b37d..ecd5f9266 100644 --- a/src/gateway/index.js +++ b/src/gateway/index.js @@ -18,6 +18,7 @@ */ module.exports = { - createMediaWikiApiGateway: require( './mediawiki' ), - createRESTBaseGateway: require( './rest' ) + mwApiPlain: require( './plain/mediawiki' ), + restbasePlain: require( './plain/rest' ), + restbaseHTML: require( './html/rest' ) }; diff --git a/src/gateway/mediawiki.js b/src/gateway/plain/mediawiki.js similarity index 86% rename from src/gateway/mediawiki.js rename to src/gateway/plain/mediawiki.js index 402963e18..44a615ef8 100644 --- a/src/gateway/mediawiki.js +++ b/src/gateway/plain/mediawiki.js @@ -13,8 +13,8 @@ // // FIXME: Move this to src/constants.js. var CACHE_LIFETIME = 300, - createModel = require( '../preview/model' ).createModel, - plainTextHTMLizer = require( '../formatter' ).htmlize, + modelBuilder = require( '../../preview/model' ), + formatter = require( '../../formatter' ), $ = jQuery; /** @@ -71,7 +71,7 @@ module.exports = function createMediaWikiApiGateway( api, config ) { function getPageSummary( title ) { return fetch( title ) .then( extractPageFromResponse ) - .then( htmlize ) + .then( formatPlainTextExtract ) .then( convertPageToModel ); } @@ -80,7 +80,7 @@ module.exports = function createMediaWikiApiGateway( api, config ) { extractPageFromResponse: extractPageFromResponse, convertPageToModel: convertPageToModel, getPageSummary: getPageSummary, - htmlize: htmlize + formatPlainTextExtract: formatPlainTextExtract }; }; @@ -107,16 +107,16 @@ function extractPageFromResponse( data ) { } /** - * HTMLize plain text response + * Make plain text nicer by applying formatter. * * @function - * @name MediaWikiGateway#htmlize + * @name MediaWikiGateway#formatPlainTextExtract * @param {Object} data The response * @returns {Object} */ -function htmlize( data ) { +function formatPlainTextExtract( data ) { var result = $.extend( {}, data ); - result.extract = plainTextHTMLizer( data.extract, data.title ); + result.extract = formatter.formatPlainTextExtract( data.extract, data.title ); return result; } @@ -129,7 +129,7 @@ function htmlize( data ) { * @returns {PreviewModel} */ function convertPageToModel( page ) { - return createModel( + return modelBuilder.createModel( page.title, page.canonicalurl, page.pagelanguagehtmlcode, diff --git a/src/gateway/plain/rest.js b/src/gateway/plain/rest.js new file mode 100644 index 000000000..4cfe0c2a0 --- /dev/null +++ b/src/gateway/plain/rest.js @@ -0,0 +1,30 @@ +var formatter = require( '../../formatter' ), + restbaseProvider = require( '../restProvider' ); + +/** + * Creates an instance of the RESTBase gateway that returns plain text + * + * This gateway differs from the {@link MediaWikiGateway MediaWiki gateway} in + * that it fetches page data from [the RESTBase page summary endpoint][0]. + * + * [0]: https://en.wikipedia.org/api/rest_v1/#!/Page_content/get_page_summary_title + * + * @param {Function} ajax A function with the same signature as `jQuery.ajax` + * @param {Object} config Configuration that affects the major behavior of the + * gateway. + * @param {Number} config.THUMBNAIL_SIZE The length of the major dimension of + * the thumbnail. + * @returns {RESTBaseGateway} + */ +module.exports = function createRESTBaseGateway( ajax, config ) { + return restbaseProvider( ajax, config, parsePlainTextResponse ); +}; + +/** + * Prepare extract + * @param {Object} page Rest response + * @returns {Array} An array of DOM Elements + */ +function parsePlainTextResponse( page ) { + return formatter.formatPlainTextExtract( page.extract, page.title ); +} diff --git a/src/gateway/rest.js b/src/gateway/restProvider.js similarity index 88% rename from src/gateway/rest.js rename to src/gateway/restProvider.js index 7406deb8e..623e1a59c 100644 --- a/src/gateway/rest.js +++ b/src/gateway/restProvider.js @@ -4,11 +4,9 @@ var RESTBASE_ENDPOINT = '/api/rest_v1/page/summary/', RESTBASE_PROFILE = 'https://www.mediawiki.org/wiki/Specs/Summary/1.2.0', - createModel = require( '../preview/model' ).createModel, - plainTextHTMLizer = require( '../formatter' ).htmlize, + modelBuilder = require( '../preview/model' ), mw = window.mediaWiki, $ = jQuery; - /** * @interface RESTBaseGateway * @extends Gateway @@ -29,9 +27,10 @@ var RESTBASE_ENDPOINT = '/api/rest_v1/page/summary/', * gateway. * @param {Number} config.THUMBNAIL_SIZE The length of the major dimension of * the thumbnail. + * @param {Function} extractParser A function that takes response and returns parsed extract * @returns {RESTBaseGateway} */ -module.exports = function createRESTBaseGateway( ajax, config ) { +module.exports = function createRESTBaseGateway( ajax, config, extractParser ) { /** * Fetches page data from [the RESTBase page summary endpoint][0]. @@ -60,17 +59,13 @@ module.exports = function createRESTBaseGateway( ajax, config ) { .then( function ( page ) { result.resolve( - convertPageToModel( page, config.THUMBNAIL_SIZE ) ); + convertPageToModel( page, config.THUMBNAIL_SIZE, extractParser ) ); }, function ( jqXHR ) { if ( jqXHR.status === 404 ) { + result.resolve( - convertPageToModel( { - title: title, - lang: '', - dir: '', - extract: '' - }, 0 ) + modelBuilder.createNullModel( title ) ); } else { result.reject(); @@ -117,7 +112,7 @@ function generateThumbnailData( thumbnail, original, thumbSize ) { // where the thumbnail's extension is .svg.png. filename = lastPart.substr( lastPart.indexOf( 'px-' ) + 3 ); - // Scale the thumbnail's largest dimension. + // Scale the thumbnail's largest dimension. if ( thumbnail.width > thumbnail.height ) { width = thumbSize; height = Math.floor( ( thumbSize / thumbnail.width ) * thumbnail.height ); @@ -148,15 +143,16 @@ function generateThumbnailData( thumbnail, original, thumbSize ) { * @name RESTBaseGateway#convertPageToModel * @param {Object} page * @param {Number} thumbSize + * @param {Function} extractParser * @returns {PreviewModel} */ -function convertPageToModel( page, thumbSize ) { - return createModel( +function convertPageToModel( page, thumbSize, extractParser ) { + return modelBuilder.createModel( page.title, new mw.Title( page.title ).getUrl(), page.lang, page.dir, - plainTextHTMLizer( page.extract, page.title ), + extractParser( page ), page.thumbnail ? generateThumbnailData( page.thumbnail, page.originalimage, thumbSize ) : undefined ); } diff --git a/src/index.js b/src/index.js index 25f1fb8c3..b011658a9 100644 --- a/src/index.js +++ b/src/index.js @@ -8,8 +8,7 @@ var mw = mediaWiki, ReduxThunk = require( 'redux-thunk' ), constants = require( './constants' ), - createRESTBaseGateway = require( './gateway/rest' ), - createMediaWikiApiGateway = require( './gateway/mediawiki' ), + gatewayBuilder = require( './gateway/index' ), createUserSettings = require( './userSettings' ), createPreviewBehavior = require( './previewBehavior' ), createSchema = require( './schema' ), @@ -47,10 +46,16 @@ var mw = mediaWiki, * @return {ext.popups.Gateway} */ function createGateway( config ) { - if ( config.get( 'wgPopupsAPIUseRESTBase' ) ) { - return createRESTBaseGateway( $.ajax, constants ); + switch ( config.get( 'wgPopupsGateway' ) ) { + case 'mwApiPlain': + return gatewayBuilder.mwApiPlain( new mw.Api(), constants ); + case 'restbasePlain': + return gatewayBuilder.restbasePlain( $.ajax, constants ); + case 'restbaseHTML': + return gatewayBuilder.restbaseHTML( $.ajax, constants ); + default: + throw new Error( 'Unknown gateway' ); } - return createMediaWikiApiGateway( new mw.Api(), constants ); } /** diff --git a/src/preview/model.js b/src/preview/model.js index 608ce1cd0..42aca7aa0 100644 --- a/src/preview/model.js +++ b/src/preview/model.js @@ -2,19 +2,15 @@ * @module preview/model */ +/** + * @constant {String} + */ var TYPE_GENERIC = 'generic', +/** + * @constant {String} + */ TYPE_PAGE = 'page'; -/** - * @constant {String} - */ -exports.TYPE_GENERIC = TYPE_GENERIC; - -/** - * @constant {String} - */ -exports.TYPE_PAGE = TYPE_PAGE; - /** * @typedef {Object} PreviewModel * @property {String} title @@ -24,11 +20,17 @@ exports.TYPE_PAGE = TYPE_PAGE; * @property {?Array} extract `undefined` if the extract isn't * viable, e.g. if it's empty after having ellipsis and parentheticals * removed - * @property {String} type Either "EXTRACT" or "GENERIC" + * @property {String} type Either "extract" or "generic" * @property {?Object} thumbnail * * @global */ +module.exports = { + TYPE_GENERIC: TYPE_GENERIC, + TYPE_PAGE: TYPE_PAGE, + createModel: createModel, + createNullModel: createNullModel +}; /** * Creates a preview model. @@ -41,7 +43,7 @@ exports.TYPE_PAGE = TYPE_PAGE; * @param {?Object} thumbnail * @return {PreviewModel} */ -exports.createModel = function createModel( +function createModel( title, url, languageCode, @@ -60,7 +62,17 @@ exports.createModel = function createModel( type: processedExtract === undefined ? TYPE_GENERIC : TYPE_PAGE, thumbnail: thumbnail }; -}; +} + +/** + * Creates an empty preview model. + * + * @param {String} title + * @return {PreviewModel} + */ +function createNullModel( title ) { + return createModel( title, '', '', '', [], '' ); +} /** * Processes the extract returned by the TextExtracts MediaWiki API query @@ -69,11 +81,11 @@ exports.createModel = function createModel( * If the extract is `undefined`, `null`, or empty, then `undefined` is * returned. * - * @param {?Array} extract - * @return {?String} + * @param {Array|undefined|null} extract + * @return {Array|undefined} Array when extract is an not empty array, undefined otherwise */ function processExtract( extract ) { - if ( extract === undefined || extract.length === 0 ) { + if ( extract === undefined || extract === null || extract.length === 0 ) { return undefined; } return extract; diff --git a/tests/node-qunit/formatter.test.js b/tests/node-qunit/formatter.test.js index 94116ca5c..a188e7f7a 100644 --- a/tests/node-qunit/formatter.test.js +++ b/tests/node-qunit/formatter.test.js @@ -55,7 +55,7 @@ QUnit.test( 'Title is bold', function ( assert ) { function test( extract, title, expected, msg ) { var $div = $( '