Page MenuHomeWickedGov Phorge

MwRestSearchClient.js
No OneTemporary

Size
4 KB
Referenced Files
None
Subscribers
None

MwRestSearchClient.js

/**
* REST API search client implementation
*
* @module MwRestSearchClient
*/
const { CitizenCommandPaletteSearchClient, CommandPaletteSearchResponse, AbortableSearchFetch } = require( '../types.js' );
const fetchJson = require( '../utils/fetch.js' );
const { cdxIconArticle, cdxIconArticleRedirect, cdxIconEdit } = require( '../icons.json' );
/**
* @typedef {Object} RestResponse
* @property {RestResult[]} pages
*/
/**
* @typedef {Object} RestResult
* @property {number} id
* @property {string} key
* @property {string} title
* @property {string | null } matched_title
* @property {string} [description]
* @property {RestThumbnail | null} [thumbnail]
*/
/**
* @typedef {Object} RestThumbnail
* @property {string} url
* @property {number | null} [width]
* @property {number | null} [height]
*/
/**
* @class
* @implements {CitizenCommandPaletteSearchClient}
*/
class MwRestSearchClient {
constructor() {
this.editMessage = mw.msg( 'action-edit' );
this.searchApiUrl = mw.config.get( 'wgScriptPath' ) + '/rest.php';
}
/**
* Processes the query
*
* @param {string} query The original search query
* @return {string} The processed query
*/
processQuery( query ) {
// Template syntax: {{Template}} -> Template:Template
const templateMatch = /^{{(.*?)(}})?$/.exec( query );
if ( templateMatch ) {
return `Template:${ templateMatch[ 1 ] }`;
}
// Wikilink syntax: [[Article]] -> Article
const wikilinkMatch = /^\[\[(.*?)(]]?)?$/.exec( query );
if ( wikilinkMatch ) {
return wikilinkMatch[ 1 ];
}
return query;
}
/**
* Adapts the REST API response to the SearchResponse format
*
* @private
* @param {string} query
* @param {RestResponse} response
* @param {boolean} showDescription
* @return {CommandPaletteSearchResponse}
*/
adaptApiResponse( query, response, showDescription ) {
return {
query,
results: response.pages.map( ( page ) => {
const thumbnail = page.thumbnail;
return {
id: `citizen-command-palette-item-page-${ page.id }`,
type: 'page',
label: page.title,
description: showDescription ? page.description : undefined,
url: mw.util.getUrl( page.matched_title ?? page.title ), // #1065, revisit when REST API has anchor field (T306150)
thumbnail: thumbnail ? {
url: thumbnail.url,
width: thumbnail.width ?? undefined,
height: thumbnail.height ?? undefined
} : undefined,
thumbnailIcon: cdxIconArticle,
metadata: page.matched_title ? [
{
icon: cdxIconArticleRedirect,
label: page.matched_title,
highlightQuery: true
}
] : undefined,
actions: [
{
id: 'edit',
label: this.editMessage,
icon: cdxIconEdit,
url: mw.util.getUrl( page.title, { action: 'edit' } )
}
],
highlightQuery: true
};
} )
};
}
/**
* @override
* @param {string} query The search term
* @param {number} [limit=10] Maximum number of results
* @param {boolean} [showDescription=true] Whether to show descriptions
* @return {AbortableSearchFetch}
*/
fetchByQuery( query, limit = 10, showDescription = true ) {
const processedQuery = this.processQuery( query );
const params = { q: processedQuery, limit: limit.toString() };
const search = new URLSearchParams( params );
const url = `${ this.searchApiUrl }/v1/search/title?${ search.toString() }`;
const result = fetchJson( url, {
headers: {
accept: 'application/json'
}
} );
const searchResponsePromise = result.fetch.then( ( /** @type {RestResponse} */ res ) => this.adaptApiResponse( query, res, showDescription ) );
return {
abort: result.abort,
fetch: searchResponsePromise
};
}
/**
* TODO: MediaWiki upstream does not support load more yet
*
* @override
* @param {string} query The search term
* @param {number} offset The number of search results that were already loaded
* @param {number} [limit=10] How many further search results to load
* @return {AbortableSearchFetch}
*/
loadMore( query, offset, limit = 10 ) {
const params = { q: query, limit: limit.toString(), offset: offset.toString() };
const search = new URLSearchParams( params );
const url = `${ this.searchApiUrl }/v1/search/title?${ search.toString() }`;
const result = fetchJson( url, {
headers: {
accept: 'application/json'
}
} );
const searchResponsePromise = result.fetch.then( ( /** @type {RestResponse} */ res ) => this.adaptApiResponse( query, res, true ) );
return {
abort: result.abort,
fetch: searchResponsePromise
};
}
}
module.exports = MwRestSearchClient;

File Metadata

Mime Type
text/plain
Expires
Aug 18 2026, 06:18 (7 w, 6 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
0e/38/b9e5e8b99c09613748c305efd23d
Default Alt Text
MwRestSearchClient.js (4 KB)

Event Timeline