gplx/scripts/templates/Common_js.wiki

505 lines
16 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* site-wide JS — VoteNY toggle is handled in Vote.js */
/* GxPlex Search — chainable filter buttons with pagination */
( function () {
if ( mw.config.get( 'wgPageName' ) !== 'GxPlex_Search' ) {
return;
}
var PAGE_SIZE = 50;
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' }
];
var DOC_QUERY = '[[Document:+]]'
+ '|?DocReference=reference'
+ '|?DocValidityArea=validityArea'
+ '|?DocScopes=scopes'
+ '|?DocName=docName'
+ '|?DocVersion=version'
+ '|?DocStatus=status'
+ '|?DocType=docType'
+ '|?DocLanguage=language'
+ '|?DocDescription=description'
+ '|?DocRestricted=restricted'
+ '|?DocSubmittedBy=submittedBy'
+ '|?DocContributors=contributors'
+ '|limit=500'
+ '|sort=DocName'
+ '|order=asc';
var REF_QUERY = '[[Reference:+]]'
+ '|?HasDocument=linkedDoc'
+ '|?HasDocument.DocReference=reference'
+ '|?HasDocument.DocValidityArea=validityArea'
+ '|?HasDocument.DocScopes=scopes'
+ '|?HasDocument.DocName=docName'
+ '|?HasDocument.DocVersion=version'
+ '|?HasDocument.DocStatus=status'
+ '|?HasDocument.DocType=docType'
+ '|?HasDocument.DocLanguage=language'
+ '|?HasDocument.DocDescription=description'
+ '|?HasDocument.DocRestricted=restricted'
+ '|?HasDocument.DocSubmittedBy=submittedBy'
+ '|?HasDocument.DocContributors=contributors'
+ '|?RefScope=refScope'
+ '|?RefLanguage=refLanguage'
+ '|?RefTagsEN=tagsEN'
+ '|limit=500';
var allResults = [];
var filteredResults = [];
var currentPage = 1;
var activeFilters = {};
/* ---- helpers ---- */
function esc( str ) {
return String( str || '' )
.replace( /&/g, '&' )
.replace( /</g, '&lt;' )
.replace( />/g, '&gt;' )
.replace( /"/g, '&quot;' );
}
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 String( 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 || {};
var docName = getProp( p, 'docName' );
return {
_type: type,
fulltext: page.fulltext,
url: page.fullurl,
displayName: type === 'Document'
? ( docName || page.fulltext.replace( /^Document:/, '' ) )
: page.fulltext.replace( /^Reference:/, '' ),
reference: getProp( p, 'reference' ),
validityArea: getProp( p, 'validityArea' ),
scopes: getProp( p, 'scopes' ),
docName: docName,
version: getProp( p, 'version' ),
status: getProp( p, 'status' ),
docType: getProp( p, 'docType' ),
language: getProp( p, 'language' ),
description: getProp( p, 'description' ),
restricted: getProp( p, 'restricted' ),
submittedBy: getProp( p, 'submittedBy' ),
contributors: getProp( p, 'contributors' ),
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 );
}
} );
}
if ( ++done === batches.length ) { callback(); }
} );
} );
}
/* ---- filter button data ---- */
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;
}
/* ---- render filter buttons ---- */
function renderFilterButtons( filterSection, values ) {
filterSection.innerHTML = '';
activeFilters = {};
var hasAnyValue = FILTER_GROUPS.some( function ( g ) {
return Object.keys( values[ g.key ] || {} ).length > 0;
} );
if ( !hasAnyValue ) {
var msg = document.createElement( 'p' );
msg.className = 'gplx-no-results';
msg.textContent = 'No documents or references indexed yet. Import data to enable filters.';
filterSection.appendChild( msg );
return;
}
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 textEl = document.getElementById( 'gplx-filter-text' );
var text = textEl ? textEl.value.trim().toLowerCase() : '';
return results.filter( function ( r ) {
if ( !matchesFilters( r ) ) { return false; }
if ( text ) {
var hay = [
r.displayName, r.reference, r.validityArea, r.scopes,
r.docName, r.version, r.status, r.docType, r.language,
r.description, r.restricted, r.submittedBy, r.contributors,
r.preview
].join( ' ' ).toLowerCase();
if ( hay.indexOf( text ) === -1 ) { return false; }
}
return true;
} );
}
/* ---- pagination ---- */
function getPageRange( current, total ) {
if ( total <= 7 ) {
var pages = [];
for ( var i = 1; i <= total; i++ ) { pages.push( i ); }
return pages;
}
var result = [ 1 ];
var left = current - 2;
var right = current + 2;
if ( left > 2 ) { result.push( '...' ); }
for ( var j = Math.max( 2, left ); j <= Math.min( total - 1, right ); j++ ) {
result.push( j );
}
if ( right < total - 1 ) { result.push( '...' ); }
result.push( total );
return result;
}
function renderPagination( total ) {
var el = document.getElementById( 'gplx-pagination' );
if ( !el ) { return; }
var totalPages = Math.ceil( total / PAGE_SIZE );
if ( totalPages <= 1 ) { el.innerHTML = ''; return; }
var frag = document.createDocumentFragment();
function pageBtn( label, page, isCurrent, isDisabled ) {
var btn = document.createElement( 'button' );
btn.type = 'button';
btn.className = 'gplx-page-btn' + ( isCurrent ? ' gplx-page-btn--current' : '' );
btn.textContent = label;
btn.disabled = isDisabled || isCurrent;
if ( isCurrent ) { btn.setAttribute( 'aria-current', 'page' ); }
if ( !isDisabled && !isCurrent ) {
btn.addEventListener( 'click', function () {
currentPage = page;
renderPage();
var s = document.getElementById( 'gplx-search-status' );
if ( s ) { s.scrollIntoView( { behavior: 'smooth', block: 'nearest' } ); }
} );
}
return btn;
}
frag.appendChild( pageBtn( ' Previous', currentPage - 1, false, currentPage === 1 ) );
getPageRange( currentPage, totalPages ).forEach( function ( p ) {
if ( p === '...' ) {
var span = document.createElement( 'span' );
span.className = 'gplx-page-ellipsis';
span.textContent = '…';
frag.appendChild( span );
} else {
frag.appendChild( pageBtn( String( p ), p, p === currentPage, false ) );
}
} );
frag.appendChild( pageBtn( 'Next ', currentPage + 1, false, currentPage === totalPages ) );
el.innerHTML = '';
el.appendChild( frag );
}
/* ---- render results table ---- */
function renderTable( pageResults ) {
var container = document.getElementById( 'gplx-search-results' );
if ( !pageResults.length ) {
container.innerHTML = '<p class="gplx-no-results">No results match the selected filters.</p>';
return;
}
var rows = pageResults.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 renderPage() {
var total = filteredResults.length;
var totalPages = Math.ceil( total / PAGE_SIZE );
currentPage = Math.max( 1, Math.min( currentPage, totalPages || 1 ) );
var start = ( currentPage - 1 ) * PAGE_SIZE;
var end = Math.min( start + PAGE_SIZE, total );
if ( total === 0 ) {
setStatus( '0 results', false );
} else {
setStatus(
'Showing ' + ( start + 1 ) + '' + end + ' of ' + total
+ ' result' + ( total !== 1 ? 's' : '' ),
false
);
}
renderTable( filteredResults.slice( start, end ) );
renderPagination( total );
}
function refresh() {
filteredResults = applyFilters( allResults );
currentPage = 1;
renderPage();
}
/* ---- 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';
var pagination = document.createElement( 'nav' );
pagination.id = 'gplx-pagination';
pagination.className = 'gplx-pagination';
pagination.setAttribute( 'aria-label', 'Results pages' );
container.innerHTML = '';
container.appendChild( filterSection );
container.appendChild( clearBtn );
container.appendChild( searchGroup );
container.appendChild( status );
container.appendChild( results );
container.appendChild( pagination );
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 );
if ( allResults.length ) {
refresh();
} else {
setStatus( '', false );
document.getElementById( 'gplx-search-results' ).innerHTML = '';
}
} );
}
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 );
} );
} );
}() );