feat(search): add SMW Ask API as search backend option (#625)

* feat: add SMW Ask API as search backend option
* feat: allow namespace prefix in smw ask query
This commit is contained in:
Simon Stier 2023-05-18 19:57:43 +02:00 committed by GitHub
parent c621e26443
commit 2e3e5feb9b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 129 additions and 2 deletions

View file

@ -118,8 +118,9 @@ Name | Description | Values | Default
Name | Description | Values | Default
:--- | :--- | :--- | :---
`$wgCitizenSearchModule` | Which ResourceLoader module to use for search suggestion | `skins.citizen.search`; `mediawiki.searchSuggest`; string | `skins.citizen.search`
`$wgCitizenSearchGateway` | Which gateway to use for fetching search suggestion |`mwActionApi`; `mwRestApi`; string | `mwActionApi`
`$wgCitizenSearchGateway` | Which gateway to use for fetching search suggestion |`mwActionApi`; `mwRestApi`; `smwAskApi`; string | `mwActionApi`
`$wgCitizenSearchDescriptionSource` | Source of description text on search suggestions (only takes effect if `$wgCitizenSearchGateway` is `mwActionApi`) | `wikidata` - Use description provided by [WikibaseLib](Extension:WikibaseLib) or [ShortDescription](https://www.mediawiki.org/wiki/Extension:ShortDescription); `textextracts` - Use description provided by [TextExtracts](https://www.mediawiki.org/wiki/Extension:TextExtracts); `pagedescription` - Use description provided by [Description2](https://www.mediawiki.org/wiki/Extension:Description2) or any other extension that sets the `description` page property | `textextracts`
`$wgCitizenSearchSmwAskApiQueryTemplate` | The SMW ask query for fetching search suggestion (only takes effect if `$wgCitizenSearchGateway` is `smwAskApi`). You can replace the SMW properties (but not the mappings). | string | `'[[Display_title_of::~*${input}*]] \|?Display_title_of=displaytitle \|?Page_Image=thumbnail \|?Description=desc'`
`$wgCitizenMaxSearchResults` | Max number of search suggestions | Integer > 0 | `6`
### Webapp manifest

View file

@ -80,6 +80,7 @@ class ResourceLoaderHooks {
'wgCitizenSearchGateway' => $config->get( 'CitizenSearchGateway' ),
'wgCitizenSearchDescriptionSource' => $config->get( 'CitizenSearchDescriptionSource' ),
'wgCitizenMaxSearchResults' => $config->get( 'CitizenMaxSearchResults' ),
'wgCitizenSearchSmwAskApiQueryTemplate' => $config->get( 'CitizenSearchSmwAskApiQueryTemplate' ),
'wgScriptPath' => $config->get( 'ScriptPath' ),
'wgSearchSuggestCacheExpiry' => $config->get( 'SearchSuggestCacheExpiry' ),
'isMediaSearchExtensionEnabled' => ExtensionRegistry::getInstance()->isLoaded( 'MediaSearch' ),

View file

@ -21,6 +21,8 @@ function getGateway() {
return require( './mwActionApi.js' );
case 'mwRestApi':
return require( './mwRestApi.js' );
case 'smwAskApi':
return require( './smwAskApi.js' );
default:
throw new Error( 'Unknown search gateway' );
}

View file

@ -0,0 +1,116 @@
const config = require( '../config.json' );
/**
* Build URL used for fetch request
*
* @param {string} input
* @return {string} url
*/
function getUrl( input ) {
const endpoint = config.wgScriptPath + '/api.php?format=json',
maxResults = config.wgCitizenMaxSearchResults,
askQueryTemplate = config.wgCitizenSearchSmwAskApiQueryTemplate
let askQuery = '';
if ( input.includes( ':' ) ) {
let namespace = input.split( ':' )[0];
if ( namespace === 'Category' ) namespace = ':' + namespace;
input = input.split( ':' )[1];
askQuery += '[[' + namespace + ':+]]';
}
askQuery += askQueryTemplate.replaceAll( '${input}', input );
askQuery += '|limit=' + maxResults;
const query = {
action: 'ask',
query: encodeURIComponent(askQuery),
};
let queryString = '';
for ( const property in query ) {
queryString += '&' + property + '=' + query[ property ];
}
return endpoint + queryString;
}
/**
* Map raw response to Results object
*
* @param {Object} data
* @return {Object} Results
*/
function convertDataToResults( data ) {
const userLang = mw.config.get( 'wgUserLanguage' );
const getDisplayTitle = ( item ) => {
if ( item.printouts.displaytitle && item.printouts.displaytitle.length
&& item.printouts.displaytitle[0]['Language code'] && item.printouts.displaytitle[0]['Text'].item.length ) {
// multi-lang string preference: user lang => English => first result
let textEN = "";
let textResult = "";
for ( const text of item.printouts.displaytitle ) {
if ( text['Language code'].item[0] === userLang ) textResult = text['Text'].item[0];
if ( text['Language code'].item[0] === 'en' ) textEN = text['Text'].item[0];
}
if ( textResult === "" ) textResult = textEN;
if ( textResult === "" ) textResult = item.printouts.displaytitle[0]['Text'].item[0];
return textResult;
} else if ( item.printouts.displaytitle && item.printouts.displaytitle.length ) {
return item.printouts.displaytitle[0];
} else if ( item.displaytitle && item.displaytitle !== "") {
return item.displaytitle;
}
else return item.fulltext;
};
const getDescription = ( item ) => {
if ( item.printouts.desc && item.printouts.desc.length
&& item.printouts.desc[0]['Language code'] && item.printouts.desc[0]['Text'].item.length ) {
// multi-lang string preference: user lang => English => first result
let textEN = "";
let textResult = "";
for ( const text of item.printouts.desc ) {
if ( text['Language code'].item[0] === userLang ) textResult = text['Text'].item[0];
if ( text['Language code'].item[0] === 'en' ) textEN = text['Text'].item[0];
}
if ( textResult === "" ) textResult = textEN;
if ( textResult === "" ) textResult = item.printouts.desc[0]['Text'].item[0];
return textResult;
} else if ( item.printouts.desc && item.printouts.desc.length ) {
return item.printouts.desc[0];
}
else return "";
};
const getThumbnail = ( item ) => {
if ( item.printouts.thumbnail && item.printouts.thumbnail.length ) {
let img_title = item.printouts.thumbnail[0].fulltext;
return config.wgScriptPath + '/index.php?title=Special:Redirect/file/' + img_title + '&width=200&height=200';
}
else return undefined;
};
const results = [];
data = Object.values( data.query.results );
for ( let i = 0; i < data.length; i++ ) {
results[ i ] = {
id: i,
key: data[ i ].fulltext,
title: getDisplayTitle( data[ i ] ),
desc: getDescription( data[ i ] ),
thumbnail: getThumbnail( data[ i] ),
};
}
return results;
}
module.exports = {
getUrl: getUrl,
convertDataToResults: convertDataToResults
};

View file

@ -157,7 +157,8 @@
"resources/skins.citizen.search/typeahead.js",
"resources/skins.citizen.search/gateway/gateway.js",
"resources/skins.citizen.search/gateway/mwActionApi.js",
"resources/skins.citizen.search/gateway/mwRestApi.js"
"resources/skins.citizen.search/gateway/mwRestApi.js",
"resources/skins.citizen.search/gateway/smwAskApi.js"
],
"messages": [
"citizen-search-fulltext",
@ -563,6 +564,12 @@
"descriptionmsg": "citizen-config-maxsearchresults",
"public": true
},
"SearchSmwAskApiQueryTemplate": {
"value": "[[Display_title_of::~*${input}*]]|?Display_title_of=displaytitle|?Page_Image=thumbnail|?Description=desc",
"description": "The SMW ask query for fetching search suggestion. You can replace the SMW properties (but not the mappings).",
"descriptionmsg": "citizen-config-smwaskapiquerytemplate",
"public": true
},
"ShowPageTools": {
"value": true,
"description": "Page tools visibility condition",