/** @module smwAskApiSearchClient */ const fetchJson = require( '../fetch.js' ); const urlGenerator = require( '../urlGenerator.js' ); /** * @typedef {Object} SearchResponse * @property {string} query * @property {SearchResult[]} results */ /** * Helper method to get the first string value * SMW return can be be empty, string, array, undefined, null * * @param {string} s * @return {string} */ function getFirstString( s ) { if ( s === null || s === undefined || s.length === 0 ) { return undefined; } if ( typeof s === 'string' ) { return s; } if ( Array.isArray( s ) ) { for ( let i = 0; i < s.length; i++ ) { const result = getFirstString( s[ i ] ); if ( result !== undefined ) { return result; } } } return undefined; } /** * @param {MwMap} config * @param {string} query * @param {Object} response * @param {boolean} showDescription * @return {SearchResponse} */ function adaptApiResponse( config, query, response, showDescription ) { const urlGeneratorInstance = urlGenerator( config ); const getDescription = ( page ) => { return getFirstString( page?.printouts?.Description ).slice( 0, 60 ); }; return { query, results: response.query.results.map( ( page ) => { page = Object.values( page )[ 0 ]; const thumbnail = page.printouts.Page_Image[ 0 ]; return { id: undefined, label: getFirstString( page.displaytitle ) || page.fulltext, key: page.fulltext.replace( / /g, '_' ), title: page.fulltext, description: showDescription ? getDescription( page ) : undefined, url: urlGeneratorInstance.generateUrl( page ), thumbnail: thumbnail ? { url: `${ config.wgScriptPath }/index.php?title=Special:Redirect/file/${ thumbnail.fulltext }&width=200&height=200`, width: 200, height: 200 } : undefined }; } ) ?? [] }; } /** * @typedef {Object} AbortableSearchFetch * @property {Promise} fetch * @property {Function} abort */ /** * @callback fetchByTitle * @param {string} query The search term. * @param {number} [limit] Maximum number of results. * @return {AbortableSearchFetch} */ /** * @callback loadMore * @param {string} query The search term. * @param {number} offset The number of search results that were already loaded. * @param {number} [limit] How many further search results to load (at most). * @return {AbortableSearchFetch} */ /** * @typedef {Object} SearchClient * @property {fetchByTitle} fetchByTitle * @property {loadMore} [loadMore] */ /** * @param {MwMap} config * @return {SearchClient} */ function smwAskApiSearchClient( config ) { return { /** * @type {fetchByTitle} */ // FIXME: Set showDescription to false for now, the 'Description' key in printout seems to be causing issue fetchByTitle: ( q, limit = config.wgCitizenMaxSearchResults, showDescription = false ) => { const searchApiUrl = config.wgScriptPath + '/api.php'; const getConditions = () => { const separateConditions = ( s ) => { return s.replace( /\]\]\s*\[\[/g, '|' ); }; const removeSquareBrackets = ( s ) => { return s.replace( /\[|\]/g, '' ); }; const conditions = removeSquareBrackets( separateConditions( q ) ); return conditions; }; const getPrintouts = () => { /* * FIXME: Figure out how to assign a label to printout statement in askargs * TODO: Should let user define what property is used for description and thumbnail */ const printouts = [ /* Extension:PageImages and Extension:SemanticExtraSpecialProperties */ 'Page_Image' ]; if ( showDescription === true ) { /* Extension:Semantic Meta Tags */ printouts.push( 'Description' ); } return printouts.join( '|' ); }; // @see https://www.semantic-mediawiki.org/wiki/Help:API:askargs /* eslint-disable camelcase */ const params = { format: 'json', formatversion: '2', action: 'askargs', api_version: '3', conditions: getConditions(), printouts: getPrintouts(), parameters: `limit=${ limit.toString() }` }; /* eslint-enable camelcase */ const search = new URLSearchParams( params ); const url = `${ searchApiUrl }?${ search.toString() }`; const result = fetchJson( url, { headers: { accept: 'application/json' } } ); const searchResponsePromise = result.fetch .then( ( /** @type {SMWAskArgResponse} */ res ) => { return adaptApiResponse( config, q, res, showDescription ); } ); return { abort: result.abort, fetch: searchResponsePromise }; } }; } module.exports = smwAskApiSearchClient;