MediaWiki:Common.js: Difference between revisions
No edit summary |
No edit summary |
||
| Line 922: | Line 922: | ||
wireUllekhaLinks(); | wireUllekhaLinks(); | ||
} | } | ||
}() );/* ── Search result highlight ──────────────────────────────────── */ | }() ); | ||
/* ── Search result highlight ──────────────────────────────────── */ | |||
/* Replace the ENTIRE search highlight IIFE in MediaWiki:Common.js | |||
* (from the line "( function () {" just before "function storeQueryForLink" | |||
* to its closing "}() );" ) | |||
* with this block. | |||
* | |||
* FIXES vs previous version: | |||
* 1. Path comparison now also stores wgPageName (title) as fallback, | |||
* avoiding any pathname encoding mismatch. | |||
* 2. sessionStorage key is NOT removed until after highlight succeeds | |||
* (or max retries exhausted) — prevents race with wikipage.content hook. | |||
* 3. Retry logic waits for .mw-parser-output to actually exist in DOM. | |||
* 4. Mobile bar is full-width terracotta (matches annotation bar style). | |||
*/ | |||
( function () { | ( function () { | ||
| Line 931: | Line 945: | ||
sessionStorage.setItem( 'gr_search_hl', JSON.stringify({ | sessionStorage.setItem( 'gr_search_hl', JSON.stringify({ | ||
query: query, | query: query, | ||
pathname: a.pathname | pathname: a.pathname, | ||
/* Also store the decoded title as extra fallback */ | |||
title: decodeURIComponent( a.pathname.replace( /^\//, '' ) ).replace( /_/g, ' ' ) | |||
}) ); | }) ); | ||
} catch(e) {} | } catch(e) {} | ||
| Line 943: | Line 959: | ||
if ( !stored || !stored.query ) return; | if ( !stored || !stored.query ) return; | ||
var currentPath = window.location.pathname; | /* ── Path / title check ──────────────────────────────────────── | ||
var storedPath | * Match if: | ||
* (a) pathnames match after normalisation, OR | |||
* (b) stored title matches current wgPageName (most reliable) | |||
* ──────────────────────────────────────────────────────────── */ | |||
var currentPath = window.location.pathname; | |||
var storedPath = stored.pathname || ''; | |||
function normPath(p) { | function normPath(p) { | ||
return decodeURIComponent(p).replace(/\/+$/, '').replace(/_/g,' '); | return decodeURIComponent(p).replace(/\/+$/, '').replace(/_/g, ' ').toLowerCase(); | ||
} | } | ||
var | |||
var pathMatch = storedPath && normPath(storedPath) === normPath(currentPath); | |||
/* | |||
/* wgPageName fallback — most reliable on all URL schemes */ | |||
var currentTitle = ( window.mw && mw.config && mw.config.get('wgPageName') || '' ) | |||
.replace(/_/g, ' '); | |||
var storedTitle = stored.title || normPath(storedPath).replace(/^\//, ''); | |||
var titleMatch = storedTitle && currentTitle && | |||
currentTitle.toLowerCase() === storedTitle.toLowerCase(); | |||
if ( storedPath && !pathMatch && !titleMatch ) { | |||
/* Not the right page — clear and bail */ | |||
try { sessionStorage.removeItem( 'gr_search_hl' ); } catch(e) {} | try { sessionStorage.removeItem( 'gr_search_hl' ); } catch(e) {} | ||
return; | return; | ||
| Line 961: | Line 989: | ||
if ( !query ) return; | if ( !query ) return; | ||
try { sessionStorage.removeItem( 'gr_search_hl' ); } catch(e) {} | /* ── Retry until .mw-parser-output is in the DOM ────────────── | ||
* Don't clear sessionStorage until highlight actually ran — | |||
* the wikipage.content hook may fire before DOMContentLoaded | |||
* on some MW setups. | |||
* ──────────────────────────────────────────────────────────── */ | |||
var attempts = 0; | |||
var MAX_ATTEMPTS = 8; | |||
function tryHighlight() { | |||
attempts++; | |||
var content = document.querySelector( '#mw-content-text .mw-parser-output' ); | |||
if ( !content && attempts < MAX_ATTEMPTS ) { | |||
setTimeout( tryHighlight, attempts * 200 ); | |||
return; | |||
} | |||
/* Clear key now — we're committed to running */ | |||
try { sessionStorage.removeItem( 'gr_search_hl' ); } catch(e) {} | |||
if ( !content ) return; | |||
highlightText( query ); | |||
} | |||
/* First attempt immediately, then back off */ | |||
tryHighlight(); | |||
} | } | ||
| Line 980: | Line 1,023: | ||
if ( !raw ) return; | if ( !raw ) return; | ||
var patterns = [ | /* Build patterns: full phrase first, then individual words */ | ||
var patterns = [ escapeRegex( raw ) ]; | |||
raw.split( /\s+/ ).forEach( function(w) { | raw.split( /\s+/ ).forEach( function(w) { | ||
if ( w.length >= 2 ) patterns.push( escapeRegex( w ) ); | if ( w.length >= 2 ) patterns.push( escapeRegex( w ) ); | ||
| Line 991: | Line 1,034: | ||
try { re = new RegExp( '(' + patterns[pi] + ')', 'gi' ); } | try { re = new RegExp( '(' + patterns[pi] + ')', 'gi' ); } | ||
catch(e) { continue; } | catch(e) { continue; } | ||
var count = wrapMatches( content, re ); | var count = wrapMatches( content, re ); | ||
if ( count > 0 ) { matched = true; break; } | if ( count > 0 ) { matched = true; break; } | ||
| Line 1,000: | Line 1,042: | ||
var first = document.querySelector( '.gr-search-hl' ); | var first = document.querySelector( '.gr-search-hl' ); | ||
if ( first ) { | if ( first ) { | ||
/* Small delay so page layout is stable before scrolling */ | |||
setTimeout( function() { | setTimeout( function() { | ||
first.classList.remove( 'gr-search-hl-pulse' ); | first.scrollIntoView({ behavior: 'smooth', block: 'center' }); | ||
}, | first.classList.add( 'gr-search-hl-pulse' ); | ||
setTimeout( function() { | |||
first.classList.remove( 'gr-search-hl-pulse' ); | |||
}, 2000 ); | |||
}, 150 ); | |||
} | } | ||
| Line 1,024: | Line 1,069: | ||
if ( tag === 'SCRIPT' || tag === 'STYLE' || tag === 'NOSCRIPT' ) return NodeFilter.FILTER_REJECT; | if ( tag === 'SCRIPT' || tag === 'STYLE' || tag === 'NOSCRIPT' ) return NodeFilter.FILTER_REJECT; | ||
if ( p.classList.contains( 'gr-search-hl' ) ) return NodeFilter.FILTER_REJECT; | if ( p.classList.contains( 'gr-search-hl' ) ) return NodeFilter.FILTER_REJECT; | ||
/* Skip nodes inside the static bar / UI chrome */ | |||
if ( p.closest && p.closest( '#gr-static-bar, .gr-controls, #gra-fab, #gra-mobile-bar' ) ) return NodeFilter.FILTER_REJECT; | |||
return NodeFilter.FILTER_ACCEPT; | return NodeFilter.FILTER_ACCEPT; | ||
} | } | ||
| Line 1,055: | Line 1,102: | ||
frag.appendChild( document.createTextNode( val.slice( last ) ) ); | frag.appendChild( document.createTextNode( val.slice( last ) ) ); | ||
} | } | ||
textNode.parentNode.replaceChild( frag, textNode ); | if ( textNode.parentNode ) { | ||
textNode.parentNode.replaceChild( frag, textNode ); | |||
} | |||
} ); | } ); | ||
| Line 1,062: | Line 1,111: | ||
function showDismissBar( query ) { | function showDismissBar( query ) { | ||
var isMob = window.innerWidth < 768; | /* Remove any existing bar */ | ||
var existing = document.getElementById( 'gr-hl-bar' ); | |||
if ( existing ) existing.remove(); | |||
var isMob = window.innerWidth < 768 || | |||
!!document.getElementById( 'mw-mf-viewport' ); | |||
var bar = document.createElement( 'div' ); | var bar = document.createElement( 'div' ); | ||
bar.id = 'gr-hl-bar'; | bar.id = 'gr-hl-bar'; | ||
var count = document.querySelectorAll( '.gr-search-hl' ).length; | |||
if ( !count ) return; /* Nothing to navigate */ | |||
if ( isMob ) { | if ( isMob ) { | ||
/* ── Mobile: full-width terracotta bottom bar ── */ | |||
bar.style.cssText = [ | bar.style.cssText = [ | ||
'position:fixed', 'bottom:0', 'left:0', 'right:0', | 'position:fixed', 'bottom:0', 'left:0', 'right:0', | ||
| Line 1,075: | Line 1,134: | ||
'font-family:system-ui,sans-serif', | 'font-family:system-ui,sans-serif', | ||
'box-shadow:0 -2px 12px rgba(0,0,0,0.25)', | 'box-shadow:0 -2px 12px rgba(0,0,0,0.25)', | ||
'height:56px' | |||
].join(';'); | ].join(';'); | ||
var mobBtnStyle = 'flex:1;display:flex;flex-direction:column;align-items:center;' + | var mobBtnStyle = 'flex:1;display:flex;flex-direction:column;align-items:center;' + | ||
'justify-content:center;background:none;border:none;color:#fff;cursor:pointer;' + | 'justify-content:center;background:none;border:none;color:#fff;cursor:pointer;' + | ||
'padding:6px 4px;font-family:system-ui,sans-serif;font-size:11px;font-weight:500;' + | 'padding:6px 4px;font-family:system-ui,sans-serif;font-size:11px;font-weight:500;' + | ||
'gap:3px | 'gap:3px;-webkit-tap-highlight-color:rgba(0,0,0,0.12);'; | ||
bar.innerHTML = | bar.innerHTML = | ||
'<button id="gr-hl-prev" style="' + mobBtnStyle + '">' + | '<button id="gr-hl-prev" style="' + mobBtnStyle + '">' + | ||
| Line 1,110: | Line 1,159: | ||
'<span>Close</span>' + | '<span>Close</span>' + | ||
'</button>'; | '</button>'; | ||
} else { | } else { | ||
/* ── Desktop: bottom banner ── */ | |||
bar.style.cssText = [ | |||
'position:fixed', 'bottom:0', 'left:0', 'right:0', 'z-index:10200', | |||
'background:#b5451b', 'color:#fff', 'padding:10px 16px', | |||
'display:flex', 'align-items:center', 'justify-content:space-between', | |||
'font-family:system-ui,sans-serif', 'font-size:14px', | |||
'box-shadow:0 -2px 8px rgba(0,0,0,0.2)' | |||
].join(';'); | |||
var nav = document.createElement( 'div' ); | var nav = document.createElement( 'div' ); | ||
nav.style.cssText = 'display:flex;align-items:center;gap:12px;'; | nav.style.cssText = 'display:flex;align-items:center;gap:12px;'; | ||
| Line 1,119: | Line 1,178: | ||
'<button id="gr-hl-results" style="background:rgba(255,255,255,0.2);border:none;color:#fff;padding:4px 10px;border-radius:4px;cursor:pointer;font-size:13px;min-height:32px;">← Results</button>'; | '<button id="gr-hl-results" style="background:rgba(255,255,255,0.2);border:none;color:#fff;padding:4px 10px;border-radius:4px;cursor:pointer;font-size:13px;min-height:32px;">← Results</button>'; | ||
bar.appendChild( nav ); | bar.appendChild( nav ); | ||
var dismissBtn = document.createElement( 'button' ); | |||
dismissBtn.id = 'gr-hl-dismiss'; | |||
dismissBtn.textContent = '✕ Close'; | |||
dismissBtn.style.cssText = 'background:rgba(255,255,255,0.15);border:none;color:#fff;padding:4px 12px;border-radius:4px;cursor:pointer;font-size:13px;min-height:32px;'; | |||
bar.appendChild( dismissBtn ); | |||
} | } | ||
document.body.appendChild( bar ); | |||
/* ── Navigation ── */ | |||
var hlEls = Array.from( document.querySelectorAll( '.gr-search-hl' ) ); | |||
var current = 0; | |||
function goTo( idx ) { | function goTo( idx ) { | ||
hlEls.forEach( function(el) { el.classList.remove( 'gr-search-hl-current' ); } ); | hlEls.forEach( function(el) { el.classList.remove( 'gr-search-hl-current' ); } ); | ||
current = ( ( idx % hlEls.length ) + hlEls.length ) % hlEls.length; | |||
var el = hlEls[ | var el = hlEls[ current ]; | ||
el.classList.add( 'gr-search-hl-current' ); | el.classList.add( 'gr-search-hl-current' ); | ||
el.scrollIntoView({ behavior: 'smooth', block: 'center' }); | el.scrollIntoView({ behavior: 'smooth', block: 'center' }); | ||
} | |||
function dismiss() { | |||
clearHighlights(); | |||
bar.remove(); | |||
} | } | ||
| Line 1,152: | Line 1,208: | ||
var prevBtn = document.getElementById( 'gr-hl-prev' ); | var prevBtn = document.getElementById( 'gr-hl-prev' ); | ||
var resultsBtn = document.getElementById( 'gr-hl-results' ); | var resultsBtn = document.getElementById( 'gr-hl-results' ); | ||
if ( nextBtn ) nextBtn.onclick = function() { goTo( | var dismissEl = document.getElementById( 'gr-hl-dismiss' ); | ||
if ( prevBtn ) prevBtn.onclick = function() { goTo( | |||
if ( nextBtn ) nextBtn.onclick = function() { goTo( current + 1 ); }; | |||
if ( prevBtn ) prevBtn.onclick = function() { goTo( current - 1 ); }; | |||
if ( dismissEl ) dismissEl.onclick = dismiss; | |||
if ( resultsBtn ) resultsBtn.onclick = function() { | if ( resultsBtn ) resultsBtn.onclick = function() { | ||
bar.remove(); | bar.remove(); | ||
clearHighlights(); | |||
if ( window.showSearchDialog ) | if ( window.showSearchDialog ) window.showSearchDialog( query ); | ||
}; | }; | ||
} | } | ||
| Line 1,164: | Line 1,223: | ||
document.querySelectorAll( '.gr-search-hl' ).forEach( function( span ) { | document.querySelectorAll( '.gr-search-hl' ).forEach( function( span ) { | ||
var parent = span.parentNode; | var parent = span.parentNode; | ||
if ( !parent ) return; | |||
while ( span.firstChild ) parent.insertBefore( span.firstChild, span ); | while ( span.firstChild ) parent.insertBefore( span.firstChild, span ); | ||
parent.removeChild( span ); | parent.removeChild( span ); | ||
| Line 1,178: | Line 1,238: | ||
s.id = 'gr-hl-css'; | s.id = 'gr-hl-css'; | ||
s.textContent = [ | s.textContent = [ | ||
'.gr-search-hl{ | '.gr-search-hl{background:#fff176;color:#1a1a1a;border-radius:2px;', | ||
'padding:0 1px;box-shadow:0 0 0 1px rgba(181,69,27,0.25);}', | |||
'.gr-search-hl-current{background:#ffb300!important;', | |||
'box-shadow:0 0 0 2px #b5451b!important;}', | |||
'.gr-search-hl-current{ | |||
' | |||
'@keyframes gr-hl-pulse{', | '@keyframes gr-hl-pulse{', | ||
' | '0%{background:#ffb300;}50%{background:#fff176;}100%{background:#fff176;}}', | ||
'.gr-search-hl-pulse{animation:gr-hl-pulse 1.2s ease 2;}', | '.gr-search-hl-pulse{animation:gr-hl-pulse 1.2s ease 2;}', | ||
].join(''); | ].join(''); | ||
| Line 1,198: | Line 1,250: | ||
injectHighlightCSS(); | injectHighlightCSS(); | ||
/* Run at DOMContentLoaded AND on wikipage.content hook (SPA navigation) */ | |||
function run() { | |||
/* Small delay so mw.config is populated and DOM is ready */ | |||
setTimeout( applyHighlight, 50 ); | |||
} | |||
if ( document.readyState === 'loading' ) { | if ( document.readyState === 'loading' ) { | ||
document.addEventListener( 'DOMContentLoaded', | document.addEventListener( 'DOMContentLoaded', run ); | ||
} else { | } else { | ||
applyHighlight | run(); | ||
} | |||
/* Hook for MW SPA navigation */ | |||
if ( window.mw ) { | |||
mw.hook( 'wikipage.content' ).add( function() { | |||
setTimeout( applyHighlight, 100 ); | |||
} ); | |||
} | } | ||