MediaWiki:Common.js: Difference between revisions
From GxPlex
Gxplex admin (talk | contribs) Add Topic column to search results |
Gxplex admin (talk | contribs) Rewrite search: filter buttons, Document/Reference namespaces, text preview |
||
| Line 1: | Line 1: | ||
/* site-wide JS β VoteNY toggle is handled in Vote.js */ | /* site-wide JS β VoteNY toggle is handled in Vote.js */ | ||
/* GxPlex Search β | /* GxPlex Search β chainable filter buttons */ | ||
( function () { | ( function () { | ||
if ( mw.config.get( 'wgPageName' ) !== 'GxPlex_Search' ) { | if ( mw.config.get( 'wgPageName' ) !== 'GxPlex_Search' ) { | ||
| Line 7: | Line 7: | ||
} | } | ||
var | /* Filter dimensions shown as button groups. | ||
' | * Free-text fields (description, contributors, version) are excluded here | ||
' | * but remain searchable via the text input. */ | ||
' | var FILTER_GROUPS = [ | ||
' | { key: 'type',Β Β Β Β label: 'Type' }, | ||
' | { key: 'validityArea', label: 'Validity area' }, | ||
' | { key: 'scopes',Β Β Β label: 'Scope(s)' }, | ||
' | { key: 'status',Β Β Β label: 'Status' }, | ||
' | { key: 'docType',Β Β label: 'Document type' }, | ||
{ key: 'language',Β Β label: 'Language' }, | |||
{ key: 'restricted',Β label: 'Restricted access' }, | |||
{ key: 'submittedBy', label: 'Submitted by' } | |||
]; | ]; | ||
/* SMW property names must match the {{#set:}} calls on Document/Reference pages. */ | |||
var DOC_QUERY = '[[Document:+]]' | |||
+ '|?DocValidityArea=validityArea' | |||
+ '|?DocScopes=scopes' | |||
+ '|?DocName=docName' | |||
+ '|?DocVersion=version' | |||
+ '|?DocStatus=status' | |||
+ '|?DocType=docType' | |||
+ '|?DocLanguage=language' | |||
+ '|?DocDescription=description' | |||
+ '|?DocRestricted=restricted' | |||
+ '|?DocSubmittedBy=submittedBy' | |||
+ '|limit=500' | |||
+ '|sort=DocName' | |||
+ '|order=asc'; | |||
/* Chained properties (HasDocument.Xyz) allow filtering References | |||
* by their parent document's metadata. */ | |||
var REF_QUERY = '[[Reference:+]]' | |||
+ '|?HasDocument=linkedDoc' | |||
+ '|?HasDocument.DocValidityArea=validityArea' | |||
+ '|?HasDocument.DocScopes=scopes' | |||
+ '|?HasDocument.DocStatus=status' | |||
+ '|?HasDocument.DocType=docType' | |||
+ '|?HasDocument.DocLanguage=language' | |||
+ '|?HasDocument.DocRestricted=restricted' | |||
+ '|?HasDocument.DocSubmittedBy=submittedBy' | |||
+ '|?RefScope=refScope' | |||
+ '|?RefLanguage=refLanguage' | |||
+ '|?RefTagsEN=tagsEN' | |||
+ '|limit=500'; | |||
var allResults = []; | var allResults = []; | ||
/* activeFilters: { groupKey: { value: true } } β plain objects, no Set dependency */ | |||
var activeFilters = {}; | |||
/* ---- helpers ---- */ | /* ---- helpers ---- */ | ||
| Line 28: | Line 65: | ||
.replace( />/g, '>' ) | .replace( />/g, '>' ) | ||
.replace( /"/g, '"' ); | .replace( /"/g, '"' ); | ||
} | } | ||
| Line 50: | Line 76: | ||
} | } | ||
function splitValues( str ) { | |||
return str.split( ',' ).map( function ( v ) { return v.trim(); } ).filter( Boolean ); | |||
} | |||
/* ---- parse SMW responses ---- */ | |||
var | function parseResults( data, type ) { | ||
var raw = ( data.query && data.query.results ) || {}; | |||
return Object.keys( raw ).map( function ( key ) { | |||
var page = raw[ key ]; | |||
var p = page.printouts || {}; | |||
return { | |||
_type: type, | |||
fulltext: page.fulltext, | |||
url: page.fullurl, | |||
displayName: type === 'Document' | |||
? ( getProp( p, 'docName' ) || page.fulltext.replace( /^Document:/, '' ) ) | |||
: page.fulltext.replace( /^Reference:/, '' ), | |||
validityArea: getProp( p, 'validityArea' ), | |||
scopes:Β Β Β getProp( p, 'scopes' ), | |||
status: Β Β Β getProp( p, 'status' ), | |||
docType: Β Β getProp( p, 'docType' ), | |||
language:Β Β getProp( p, 'language' ), | |||
restricted:Β getProp( p, 'restricted' ), | |||
submittedBy: getProp( p, 'submittedBy' ), | |||
description:Β getProp( p, 'description' ), | |||
preview: '' | |||
}; | |||
} ); | |||
} | |||
/* ---- text extracts (250 chars, batches of 50) ---- */ | |||
function fetchExtracts( results, callback ) { | |||
if ( !results.length ) { callback(); return; } | |||
var | var byTitle = {}; | ||
results.forEach( function ( r ) { byTitle[ r.fulltext ] = r; } ); | |||
var titles = results.map( function ( r ) { return r.fulltext; } ); | |||
var batches = []; | |||
for ( var i = 0; i < titles.length; i += 50 ) { | |||
batches.push( titles.slice( i, i + 50 ) ); | |||
} | |||
var done = 0; | |||
var api = new mw.Api(); | |||
batches.forEach( function ( batch ) { | |||
api.get( { | |||
action: 'query', | |||
prop: 'extracts', | |||
exchars: 250, | |||
exintro: true, | |||
explaintext: true, | |||
titles: batch.join( '|' ), | |||
format: 'json' | |||
} ).always( function ( data ) { | |||
if ( data && data.query && data.query.pages ) { | |||
Object.keys( data.query.pages ).forEach( function ( id ) { | |||
var pg = data.query.pages[ id ]; | |||
if ( byTitle[ pg.title ] ) { | |||
byTitle[ pg.title ].preview = ( pg.extract || '' ).replace( /\s+/g, ' ' ).trim().slice( 0, 250 ); | |||
} | |||
} ); | |||
} | |||
done++; | |||
if ( done === batches.length ) { callback(); } | |||
} ); | |||
} ); | |||
} | } | ||
/* ---- | /* ---- build filter button UI ---- */ | ||
function | function collectFieldValues( results ) { | ||
var values = {}; | |||
FILTER_GROUPS.forEach( function ( g ) { values[ g.key ] = {}; } ); | |||
results.forEach( function ( r ) { | |||
FILTER_GROUPS.forEach( function ( g ) { | |||
var raw = g.key === 'type' ? r._type : r[ g.key ]; | |||
splitValues( raw ).forEach( function ( v ) { | |||
values[ g.key ][ v ] = true; | |||
} ); | |||
} ); | |||
} ); | |||
return values; | |||
} | } | ||
function | function renderFilterButtons( filterSection, values ) { | ||
filterSection.innerHTML = ''; | |||
activeFilters = {}; | |||
FILTER_GROUPS.forEach( function ( g ) { | |||
activeFilters[ g.key ] = {}; | |||
var | var vals = Object.keys( values[ g.key ] ).sort(); | ||
if ( !vals.length ) { return; } | |||
Β | |||
var group = document.createElement( 'div' ); | |||
group.className = 'gplx-filter-group'; | |||
. | Β | ||
var label = document.createElement( 'span' ); | |||
label.className = 'gplx-filter-label'; | |||
label.textContent = g.label; | |||
Β | |||
var row = document.createElement( 'div' ); | |||
row.className = 'gplx-filter-btn-row'; | |||
Β | |||
vals.forEach( function ( v ) { | |||
var btn = document.createElement( 'button' ); | |||
}; | btn.type = 'button'; | ||
btn.className = 'gplx-filter-btn'; | |||
btn.textContent = v; | |||
btn.setAttribute( 'aria-pressed', 'false' ); | |||
btn.addEventListener( 'click', function () { | |||
if ( activeFilters[ g.key ][ v ] ) { | |||
delete activeFilters[ g.key ][ v ]; | |||
btn.classList.remove( 'is-active' ); | |||
btn.setAttribute( 'aria-pressed', 'false' ); | |||
} else { | |||
activeFilters[ g.key ][ v ] = true; | |||
btn.classList.add( 'is-active' ); | |||
btn.setAttribute( 'aria-pressed', 'true' ); | |||
} | |||
refresh(); | |||
} ); | |||
row.appendChild( btn ); | |||
} ); | |||
Β | |||
group.appendChild( label ); | |||
group.appendChild( row ); | |||
filterSection.appendChild( group ); | |||
} ); | } ); | ||
} | } | ||
/* ---- | /* ---- filter logic: AND across groups, OR within group ---- */ | ||
function | function matchesFilters( result ) { | ||
return FILTER_GROUPS.every( function ( g ) { | |||
var active = Object.keys( activeFilters[ g.key ] || {} ); | |||
if ( !active.length ) { return true; } | |||
var fieldVal = g.key === 'type' ? result._type : ( result[ g.key ] || '' ); | |||
var parts = splitValues( fieldVal ); | |||
return active.some( function ( av ) { | |||
return parts.some( function ( p ) { | |||
return p.toLowerCase() === av.toLowerCase(); | |||
} ); | |||
} ); | } ); | ||
} ); | } ); | ||
} | } | ||
function applyFilters( results ) { | function applyFilters( results ) { | ||
var | var text = ( ( document.getElementById( 'gplx-filter-text' ) || {} ).value || '' ).trim().toLowerCase(); | ||
return results.filter( function ( r ) { | return results.filter( function ( r ) { | ||
if ( | if ( !matchesFilters( r ) ) { return false; } | ||
if ( text ) { | if ( text ) { | ||
var | var hay = [ r.displayName, r.validityArea, r.scopes, r.status, | ||
if ( | r.docType, r.language, r.description, r.preview ].join( ' ' ).toLowerCase(); | ||
if ( hay.indexOf( text ) === -1 ) { return false; } | |||
} | } | ||
return true; | return true; | ||
} ); | } ); | ||
} | } | ||
/* ---- render results table ---- */ | |||
function renderTable( results ) { | function renderTable( results ) { | ||
var container = document.getElementById( 'gplx-search-results' ); | var container = document.getElementById( 'gplx-search-results' ); | ||
if ( !results.length ) { | if ( !results.length ) { | ||
container.innerHTML = '<p class="gplx-no-results">No | container.innerHTML = '<p class="gplx-no-results">No results match the selected filters.</p>'; | ||
return; | return; | ||
} | } | ||
var rows = results.map( function ( r ) { | var rows = results.map( function ( r ) { | ||
var typeBadge = r._type === 'Document' | |||
? '<span class="gplx-badge-doc">Document</span>' | |||
: '<span class="gplx-badge-ref">Reference</span>'; | |||
var restricted = r.restricted && r.restricted.toLowerCase() === 'yes' | var restricted = r.restricted && r.restricted.toLowerCase() === 'yes' | ||
? ' <span class="gplx-badge-restricted">Restricted</span>' | ? ' <span class="gplx-badge-restricted">Restricted</span>' : ''; | ||
: ''; | var preview = r.preview | ||
? '<div class="gplx-result-preview">' + esc( r.preview ) + '</div>' : ''; | |||
return '<tr>' | return '<tr>' | ||
+ '<td><a href="' + esc( r.url ) + '">' + esc( r. | + '<td>' + typeBadge + '</td>' | ||
+ '<td>' + esc( r. | + '<td><a href="' + esc( r.url ) + '">' + esc( r.displayName ) + '</a>' | ||
+ '<td>' + esc( r. | + restricted + preview + '</td>' | ||
+ '<td>' + esc( r.validityArea ) + '</td>' | |||
+ '<td>' + esc( r.status ) + '</td>' | |||
+ '<td>' + esc( r.language ) + '</td>' | + '<td>' + esc( r.language ) + '</td>' | ||
+ '<td>' + esc( r. | + '<td>' + esc( r.docType ) + '</td>' | ||
+ '</tr>'; | + '</tr>'; | ||
} ); | } ); | ||
container.innerHTML = '<table class="wikitable sortable gplx-results-table">' | container.innerHTML = '<table class="wikitable sortable gplx-results-table">' | ||
+ '<thead><tr><th> | + '<thead><tr>' | ||
+ '<th>Type</th><th>Title</th>' | |||
+ '<th>Validity area</th><th>Status</th><th>Language</th><th>Document type</th>' | |||
+ '</tr></thead>' | |||
+ '<tbody>' + rows.join( '' ) + '</tbody>' | + '<tbody>' + rows.join( '' ) + '</tbody>' | ||
+ '</table>'; | + '</table>'; | ||
| Line 194: | Line 273: | ||
function setStatus( msg, busy ) { | function setStatus( msg, busy ) { | ||
var | var s = document.getElementById( 'gplx-search-status' ); | ||
if ( !s ) { return; } | |||
s.textContent = msg; | |||
s.className = 'gplx-search-status' + ( busy ? ' gplx-search-busy' : '' ); | |||
} | } | ||
| Line 202: | Line 282: | ||
var visible = applyFilters( allResults ); | var visible = applyFilters( allResults ); | ||
setStatus( visible.length + ' of ' + allResults.length | setStatus( visible.length + ' of ' + allResults.length | ||
+ ' | + ' result' + ( allResults.length !== 1 ? 's' : '' ), false ); | ||
renderTable( visible ); | renderTable( visible ); | ||
} | } | ||
function | /* ---- build container UI ---- */ | ||
Β | |||
document. | function buildUI( container ) { | ||
var filterSection = document.createElement( 'div' ); | |||
filterSection.className = 'gplx-filter-buttons'; | |||
Β | |||
var clearBtn = document.createElement( 'button' ); | |||
clearBtn.type = 'button'; | |||
clearBtn.className = 'gplx-filter-clear'; | |||
clearBtn.textContent = 'Clear all filters'; | |||
clearBtn.addEventListener( 'click', function () { | |||
FILTER_GROUPS.forEach( function ( g ) { activeFilters[ g.key ] = {}; } ); | |||
document.querySelectorAll( '.gplx-filter-btn' ).forEach( function ( b ) { | |||
b.classList.remove( 'is-active' ); | |||
b.setAttribute( 'aria-pressed', 'false' ); | |||
} ); | |||
refresh(); | refresh(); | ||
} ); | } ); | ||
var searchLabel = document.createElement( 'label' ); | |||
searchLabel.className = 'gplx-filter-label'; | |||
searchLabel.htmlFor = 'gplx-filter-text'; | |||
searchLabel.textContent = 'Search in titles and text'; | |||
var textInput = document.createElement( 'input' ); | |||
textInput.type = 'search'; | |||
textInput.id = 'gplx-filter-text'; | |||
textInput.className = 'gplx-filter-input'; | |||
textInput.placeholder = 'Searchβ¦'; | |||
textInput.setAttribute( 'aria-label', 'Search in titles and text' ); | |||
var searchGroup = document.createElement( 'div' ); | |||
searchGroup.className = 'gplx-filter-group gplx-filter-group--grow'; | |||
searchGroup.style.marginBottom = '0.5rem'; | |||
searchGroup.appendChild( searchLabel ); | |||
searchGroup.appendChild( textInput ); | |||
var status = document.createElement( 'p' ); | |||
status.id = 'gplx-search-status'; | |||
status.className = 'gplx-search-status'; | |||
var results = document.createElement( 'div' ); | |||
results.id = 'gplx-search-results'; | |||
container.innerHTML = ''; | |||
container.appendChild( filterSection ); | |||
container.appendChild( clearBtn ); | |||
container.appendChild( searchGroup ); | |||
container.appendChild( status ); | |||
container.appendChild( results ); | |||
return { filterSection: filterSection, textInput: textInput }; | |||
} | |||
/* ---- load data ---- */ | |||
function load( controls ) { | |||
setStatus( 'Loadingβ¦', true ); | |||
var api = new mw.Api(); | |||
var docResults = [], refResults = []; | |||
var docDone = false, refDone = false; | |||
function onBothDone() { | |||
allResults = docResults.concat( refResults ); | |||
fetchExtracts( allResults, function () { | |||
var values = collectFieldValues( allResults ); | |||
renderFilterButtons( controls.filterSection, values ); | |||
refresh(); | |||
} ); | |||
} | |||
api.get( { action: 'ask', query: DOC_QUERY, format: 'json' } ) | |||
.always( function ( data ) { | |||
if ( data && data.query ) { docResults = parseResults( data, 'Document' ); } | |||
docDone = true; | |||
if ( refDone ) { onBothDone(); } | |||
} ); | |||
api.get( { action: 'ask', query: REF_QUERY, format: 'json' } ) | |||
.always( function ( data ) { | |||
if ( data && data.query ) { refResults = parseResults( data, 'Reference' ); } | |||
refDone = true; | |||
if ( docDone ) { onBothDone(); } | |||
} ); | |||
} | } | ||
| Line 228: | Line 378: | ||
var container = document.getElementById( 'gplx-search-app' ); | var container = document.getElementById( 'gplx-search-app' ); | ||
if ( !container ) { return; } | if ( !container ) { return; } | ||
var controls = buildUI( container ); | var controls = buildUI( container ); | ||
var debounce; | var debounce; | ||
controls.textInput.addEventListener( 'input', function () { | controls.textInput.addEventListener( 'input', function () { | ||
clearTimeout( debounce ); | clearTimeout( debounce ); | ||
debounce = setTimeout( refresh, 250 ); | debounce = setTimeout( refresh, 250 ); | ||
} ); | } ); | ||
Β | load( controls ); | ||
load( | |||
} ); | } ); | ||
} ); | } ); | ||
}() ); | }() ); | ||
Revision as of 17:23, 27 June 2026
/* site-wide JS β VoteNY toggle is handled in Vote.js */
/* GxPlex Search β chainable filter buttons */
( function () {
if ( mw.config.get( 'wgPageName' ) !== 'GxPlex_Search' ) {
return;
}
/* Filter dimensions shown as button groups.
* Free-text fields (description, contributors, version) are excluded here
* but remain searchable via the text input. */
var FILTER_GROUPS = [
{ key: 'type', label: 'Type' },
{ key: 'validityArea', label: 'Validity area' },
{ key: 'scopes', label: 'Scope(s)' },
{ key: 'status', label: 'Status' },
{ key: 'docType', label: 'Document type' },
{ key: 'language', label: 'Language' },
{ key: 'restricted', label: 'Restricted access' },
{ key: 'submittedBy', label: 'Submitted by' }
];
/* SMW property names must match the {{#set:}} calls on Document/Reference pages. */
var DOC_QUERY = '[[Document:+]]'
+ '|?DocValidityArea=validityArea'
+ '|?DocScopes=scopes'
+ '|?DocName=docName'
+ '|?DocVersion=version'
+ '|?DocStatus=status'
+ '|?DocType=docType'
+ '|?DocLanguage=language'
+ '|?DocDescription=description'
+ '|?DocRestricted=restricted'
+ '|?DocSubmittedBy=submittedBy'
+ '|limit=500'
+ '|sort=DocName'
+ '|order=asc';
/* Chained properties (HasDocument.Xyz) allow filtering References
* by their parent document's metadata. */
var REF_QUERY = '[[Reference:+]]'
+ '|?HasDocument=linkedDoc'
+ '|?HasDocument.DocValidityArea=validityArea'
+ '|?HasDocument.DocScopes=scopes'
+ '|?HasDocument.DocStatus=status'
+ '|?HasDocument.DocType=docType'
+ '|?HasDocument.DocLanguage=language'
+ '|?HasDocument.DocRestricted=restricted'
+ '|?HasDocument.DocSubmittedBy=submittedBy'
+ '|?RefScope=refScope'
+ '|?RefLanguage=refLanguage'
+ '|?RefTagsEN=tagsEN'
+ '|limit=500';
var allResults = [];
/* activeFilters: { groupKey: { value: true } } β plain objects, no Set dependency */
var activeFilters = {};
/* ---- helpers ---- */
function esc( str ) {
return String( str || '' )
.replace( /&/g, '&' )
.replace( /</g, '<' )
.replace( />/g, '>' )
.replace( /"/g, '"' );
}
function getProp( printouts, key ) {
var arr = printouts[ key ];
if ( !arr || !arr.length ) { return ''; }
return arr.map( function ( v ) {
if ( typeof v === 'string' ) { return v; }
return v.item !== undefined ? v.item : ( v.fulltext || '' );
} ).filter( Boolean ).join( ', ' );
}
function splitValues( str ) {
return str.split( ',' ).map( function ( v ) { return v.trim(); } ).filter( Boolean );
}
/* ---- parse SMW responses ---- */
function parseResults( data, type ) {
var raw = ( data.query && data.query.results ) || {};
return Object.keys( raw ).map( function ( key ) {
var page = raw[ key ];
var p = page.printouts || {};
return {
_type: type,
fulltext: page.fulltext,
url: page.fullurl,
displayName: type === 'Document'
? ( getProp( p, 'docName' ) || page.fulltext.replace( /^Document:/, '' ) )
: page.fulltext.replace( /^Reference:/, '' ),
validityArea: getProp( p, 'validityArea' ),
scopes: getProp( p, 'scopes' ),
status: getProp( p, 'status' ),
docType: getProp( p, 'docType' ),
language: getProp( p, 'language' ),
restricted: getProp( p, 'restricted' ),
submittedBy: getProp( p, 'submittedBy' ),
description: getProp( p, 'description' ),
preview: ''
};
} );
}
/* ---- text extracts (250 chars, batches of 50) ---- */
function fetchExtracts( results, callback ) {
if ( !results.length ) { callback(); return; }
var byTitle = {};
results.forEach( function ( r ) { byTitle[ r.fulltext ] = r; } );
var titles = results.map( function ( r ) { return r.fulltext; } );
var batches = [];
for ( var i = 0; i < titles.length; i += 50 ) {
batches.push( titles.slice( i, i + 50 ) );
}
var done = 0;
var api = new mw.Api();
batches.forEach( function ( batch ) {
api.get( {
action: 'query',
prop: 'extracts',
exchars: 250,
exintro: true,
explaintext: true,
titles: batch.join( '|' ),
format: 'json'
} ).always( function ( data ) {
if ( data && data.query && data.query.pages ) {
Object.keys( data.query.pages ).forEach( function ( id ) {
var pg = data.query.pages[ id ];
if ( byTitle[ pg.title ] ) {
byTitle[ pg.title ].preview = ( pg.extract || '' ).replace( /\s+/g, ' ' ).trim().slice( 0, 250 );
}
} );
}
done++;
if ( done === batches.length ) { callback(); }
} );
} );
}
/* ---- build filter button UI ---- */
function collectFieldValues( results ) {
var values = {};
FILTER_GROUPS.forEach( function ( g ) { values[ g.key ] = {}; } );
results.forEach( function ( r ) {
FILTER_GROUPS.forEach( function ( g ) {
var raw = g.key === 'type' ? r._type : r[ g.key ];
splitValues( raw ).forEach( function ( v ) {
values[ g.key ][ v ] = true;
} );
} );
} );
return values;
}
function renderFilterButtons( filterSection, values ) {
filterSection.innerHTML = '';
activeFilters = {};
FILTER_GROUPS.forEach( function ( g ) {
activeFilters[ g.key ] = {};
var vals = Object.keys( values[ g.key ] ).sort();
if ( !vals.length ) { return; }
var group = document.createElement( 'div' );
group.className = 'gplx-filter-group';
var label = document.createElement( 'span' );
label.className = 'gplx-filter-label';
label.textContent = g.label;
var row = document.createElement( 'div' );
row.className = 'gplx-filter-btn-row';
vals.forEach( function ( v ) {
var btn = document.createElement( 'button' );
btn.type = 'button';
btn.className = 'gplx-filter-btn';
btn.textContent = v;
btn.setAttribute( 'aria-pressed', 'false' );
btn.addEventListener( 'click', function () {
if ( activeFilters[ g.key ][ v ] ) {
delete activeFilters[ g.key ][ v ];
btn.classList.remove( 'is-active' );
btn.setAttribute( 'aria-pressed', 'false' );
} else {
activeFilters[ g.key ][ v ] = true;
btn.classList.add( 'is-active' );
btn.setAttribute( 'aria-pressed', 'true' );
}
refresh();
} );
row.appendChild( btn );
} );
group.appendChild( label );
group.appendChild( row );
filterSection.appendChild( group );
} );
}
/* ---- filter logic: AND across groups, OR within group ---- */
function matchesFilters( result ) {
return FILTER_GROUPS.every( function ( g ) {
var active = Object.keys( activeFilters[ g.key ] || {} );
if ( !active.length ) { return true; }
var fieldVal = g.key === 'type' ? result._type : ( result[ g.key ] || '' );
var parts = splitValues( fieldVal );
return active.some( function ( av ) {
return parts.some( function ( p ) {
return p.toLowerCase() === av.toLowerCase();
} );
} );
} );
}
function applyFilters( results ) {
var text = ( ( document.getElementById( 'gplx-filter-text' ) || {} ).value || '' ).trim().toLowerCase();
return results.filter( function ( r ) {
if ( !matchesFilters( r ) ) { return false; }
if ( text ) {
var hay = [ r.displayName, r.validityArea, r.scopes, r.status,
r.docType, r.language, r.description, r.preview ].join( ' ' ).toLowerCase();
if ( hay.indexOf( text ) === -1 ) { return false; }
}
return true;
} );
}
/* ---- render results table ---- */
function renderTable( results ) {
var container = document.getElementById( 'gplx-search-results' );
if ( !results.length ) {
container.innerHTML = '<p class="gplx-no-results">No results match the selected filters.</p>';
return;
}
var rows = results.map( function ( r ) {
var typeBadge = r._type === 'Document'
? '<span class="gplx-badge-doc">Document</span>'
: '<span class="gplx-badge-ref">Reference</span>';
var restricted = r.restricted && r.restricted.toLowerCase() === 'yes'
? ' <span class="gplx-badge-restricted">Restricted</span>' : '';
var preview = r.preview
? '<div class="gplx-result-preview">' + esc( r.preview ) + '</div>' : '';
return '<tr>'
+ '<td>' + typeBadge + '</td>'
+ '<td><a href="' + esc( r.url ) + '">' + esc( r.displayName ) + '</a>'
+ restricted + preview + '</td>'
+ '<td>' + esc( r.validityArea ) + '</td>'
+ '<td>' + esc( r.status ) + '</td>'
+ '<td>' + esc( r.language ) + '</td>'
+ '<td>' + esc( r.docType ) + '</td>'
+ '</tr>';
} );
container.innerHTML = '<table class="wikitable sortable gplx-results-table">'
+ '<thead><tr>'
+ '<th>Type</th><th>Title</th>'
+ '<th>Validity area</th><th>Status</th><th>Language</th><th>Document type</th>'
+ '</tr></thead>'
+ '<tbody>' + rows.join( '' ) + '</tbody>'
+ '</table>';
}
function setStatus( msg, busy ) {
var s = document.getElementById( 'gplx-search-status' );
if ( !s ) { return; }
s.textContent = msg;
s.className = 'gplx-search-status' + ( busy ? ' gplx-search-busy' : '' );
}
function refresh() {
var visible = applyFilters( allResults );
setStatus( visible.length + ' of ' + allResults.length
+ ' result' + ( allResults.length !== 1 ? 's' : '' ), false );
renderTable( visible );
}
/* ---- build container UI ---- */
function buildUI( container ) {
var filterSection = document.createElement( 'div' );
filterSection.className = 'gplx-filter-buttons';
var clearBtn = document.createElement( 'button' );
clearBtn.type = 'button';
clearBtn.className = 'gplx-filter-clear';
clearBtn.textContent = 'Clear all filters';
clearBtn.addEventListener( 'click', function () {
FILTER_GROUPS.forEach( function ( g ) { activeFilters[ g.key ] = {}; } );
document.querySelectorAll( '.gplx-filter-btn' ).forEach( function ( b ) {
b.classList.remove( 'is-active' );
b.setAttribute( 'aria-pressed', 'false' );
} );
refresh();
} );
var searchLabel = document.createElement( 'label' );
searchLabel.className = 'gplx-filter-label';
searchLabel.htmlFor = 'gplx-filter-text';
searchLabel.textContent = 'Search in titles and text';
var textInput = document.createElement( 'input' );
textInput.type = 'search';
textInput.id = 'gplx-filter-text';
textInput.className = 'gplx-filter-input';
textInput.placeholder = 'Searchβ¦';
textInput.setAttribute( 'aria-label', 'Search in titles and text' );
var searchGroup = document.createElement( 'div' );
searchGroup.className = 'gplx-filter-group gplx-filter-group--grow';
searchGroup.style.marginBottom = '0.5rem';
searchGroup.appendChild( searchLabel );
searchGroup.appendChild( textInput );
var status = document.createElement( 'p' );
status.id = 'gplx-search-status';
status.className = 'gplx-search-status';
var results = document.createElement( 'div' );
results.id = 'gplx-search-results';
container.innerHTML = '';
container.appendChild( filterSection );
container.appendChild( clearBtn );
container.appendChild( searchGroup );
container.appendChild( status );
container.appendChild( results );
return { filterSection: filterSection, textInput: textInput };
}
/* ---- load data ---- */
function load( controls ) {
setStatus( 'Loadingβ¦', true );
var api = new mw.Api();
var docResults = [], refResults = [];
var docDone = false, refDone = false;
function onBothDone() {
allResults = docResults.concat( refResults );
fetchExtracts( allResults, function () {
var values = collectFieldValues( allResults );
renderFilterButtons( controls.filterSection, values );
refresh();
} );
}
api.get( { action: 'ask', query: DOC_QUERY, format: 'json' } )
.always( function ( data ) {
if ( data && data.query ) { docResults = parseResults( data, 'Document' ); }
docDone = true;
if ( refDone ) { onBothDone(); }
} );
api.get( { action: 'ask', query: REF_QUERY, format: 'json' } )
.always( function ( data ) {
if ( data && data.query ) { refResults = parseResults( data, 'Reference' ); }
refDone = true;
if ( docDone ) { onBothDone(); }
} );
}
/* ---- init ---- */
mw.loader.using( [ 'mediawiki.api' ], function () {
$( function () {
var container = document.getElementById( 'gplx-search-app' );
if ( !container ) { return; }
var controls = buildUI( container );
var debounce;
controls.textInput.addEventListener( 'input', function () {
clearTimeout( debounce );
debounce = setTimeout( refresh, 250 );
} );
load( controls );
} );
} );
}() );