/* 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, '>' ) .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 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 = '

No results match the selected filters.

'; return; } var rows = pageResults.map( function ( r ) { var typeBadge = r._type === 'Document' ? 'Document' : 'Reference'; var restricted = r.restricted && r.restricted.toLowerCase() === 'yes' ? ' Restricted' : ''; var preview = r.preview ? '
' + esc( r.preview ) + '
' : ''; return '' + '' + typeBadge + '' + '' + esc( r.displayName ) + '' + restricted + preview + '' + '' + esc( r.validityArea ) + '' + '' + esc( r.status ) + '' + '' + esc( r.language ) + '' + '' + esc( r.docType ) + '' + ''; } ); container.innerHTML = '' + '' + '' + '' + '' + '' + rows.join( '' ) + '' + '
TypeTitleValidity areaStatusLanguageDocument type
'; } 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 ); } ); } ); }() );