MediaWiki:Common-mainpage.js: Difference between revisions
MediaWiki interface page
More actions
No edit summary |
No edit summary |
||
| Line 108: | Line 108: | ||
var badgeStyle = series.color ? ' style="background:' + series.color + '"' : ''; | var badgeStyle = series.color ? ' style="background:' + series.color + '"' : ''; | ||
return '<div class="bsw-slide bsw-active" data-article="' + esc( title ) + '" | return '<div class="bsw-slide bsw-active" data-article="' + esc( title ) + '" data-url="' + esc( url ) + '">' + | ||
'<div class="bsw-slide-bg"></div>' + | '<div class="bsw-slide-bg"></div>' + | ||
'<div class="bsw-slide-overlay"></div>' + | '<div class="bsw-slide-overlay"></div>' + | ||
'<div class="bsw-slide-content">' + | '<div class="bsw-slide-content">' + | ||
| Line 161: | Line 159: | ||
window.bswPrevSlide = function () { goTo( current - 1 ); }; | window.bswPrevSlide = function () { goTo( current - 1 ); }; | ||
window.bswNextSlide = function () { goTo( current + 1 ); }; | window.bswNextSlide = function () { goTo( current + 1 ); }; | ||
/* Click on image area (overlay or bg) navigates to article */ | |||
slides.forEach( function ( slide ) { | |||
[ '.bsw-slide-overlay', '.bsw-slide-bg' ].forEach( function ( sel ) { | |||
var el = slide.querySelector( sel ); | |||
if ( !el ) return; | |||
el.style.cursor = 'pointer'; | |||
el.addEventListener( 'click', function () { | |||
var url = slide.getAttribute( 'data-url' ); | |||
if ( url ) window.location.href = url; | |||
} ); | |||
} ); | |||
} ); | |||
dots.forEach( function ( dot, i ) { | dots.forEach( function ( dot, i ) { | ||
Revision as of 03:12, 13 April 2026
/**
* BattlestarWiki — Main Page JavaScript * Append to MediaWiki:Common.js * * Handles: * 1. Hero slideshow auto-rotation + article image fetching * 2. Featured Article of the Day — daily deterministic pick, * excluding Category:Stub_Pages, with extract + thumbnail * 3. Media wiki file count (from media.battlestarwiki.org) * 4. Recent changes tab switching (EN / DE / Media wikis) * 5. Red-link card filtering * * All logic is scoped to .bsw-main-page; safe to load globally. */
( function () {
'use strict';
var API = 'https://en.battlestarwiki.org/w/api.php'; var FR_API = 'https://fr.battlestarwiki.ddns.net/w/api.php'; var MEDIA_API = 'https://media.battlestarwiki.org/w/api.php'; var DE_API = 'https://de.battlestarwiki.org/w/api.php';
/* ── Shared utilities ─────────────────────────────────────────── */
function dailySeed( dateStr ) {
var d;
if ( dateStr ) {
var p = dateStr.split( '-' );
d = Date.UTC( +p[0], +p[1] - 1, +p[2] );
} else {
var now = new Date();
d = Date.UTC( now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() );
}
return Math.floor( d / 86400000 );
}
function esc( s ) {
return String( s || )
.replace( /&/g, '&' )
.replace( /</g, '<' )
.replace( />/g, '>' )
.replace( /"/g, '"' );
}
function apiGetFrom( baseUrl, params ) {
params.format = 'json';
params.origin = '*';
var qs = Object.keys( params )
.map( function ( k ) { return encodeURIComponent( k ) + '=' + encodeURIComponent( params[k] ); } )
.join( '&' );
return fetch( baseUrl + '?' + qs, { headers: { Accept: 'application/json' } } )
.then( function ( r ) {
if ( !r.ok ) throw new Error( 'API HTTP ' + r.status );
return r.json();
} );
}
function apiGet( params ) {
return apiGetFrom( API, params );
}
/* ════════════════════════════════════════════════════════════════
1. HERO SLIDESHOW
Dynamically populated from the wiki's most-viewed articles.
Uses a daily seed so all visitors see the same 3 slides each day.
Falls back to the static Lua-rendered slides if the API fails.
════════════════════════════════════════════════════════════════ */
/* Series classification for badge color + label */
var SERIES_CATS = [
{ pat: /\(RDM\)|\(TRS\)|Re-imagined|Reimagined/i, label: 'Re-imagined Series', color: 'var(--color-primary)' },
{ pat: /\(TOS\)|Original Series|\bTOS\b/i, label: 'Original Series', color: '#ef9f27' },
{ pat: /\(1980\)|Galactica.?1980|Category:1980\b/i, label: 'Galactica 1980', color: '#97c459' },
{ pat: /\(Caprica\)|Caprica \(series\)|Category:Caprica\b/i, label: 'Caprica', color: '#d4537e' },
{ pat: /\(BAC\)|Blood and Chrome|Blood.*Chrome|\bBAC\b/i, label: 'Blood & Chrome', color: '#e24b4a' }
];
function classifyArticle( title, cats, extract ) {
/* Check category names first */
if ( cats ) {
for ( var i = 0; i < SERIES_CATS.length; i++ ) {
for ( var j = 0; j < cats.length; j++ ) {
if ( SERIES_CATS[i].pat.test( cats[j].title ) ) return SERIES_CATS[i];
}
}
}
/* Fall back to title matching */
for ( var k = 0; k < SERIES_CATS.length; k++ ) {
if ( SERIES_CATS[k].pat.test( title ) ) return SERIES_CATS[k];
}
/* Final fallback: scan extract for series keywords */
if ( extract ) {
var text = extract.replace( /<[^>]+>/g, );
for ( var m = 0; m < SERIES_CATS.length; m++ ) {
if ( SERIES_CATS[m].pat.test( text ) ) return SERIES_CATS[m];
}
}
return { label: 'Battlestar Wiki', color: 'var(--color-primary)' };
}
function buildSlideHTML( article, series, imgUrl ) {
var title = article.title;
var url = '/wiki/' + encodeURIComponent( title.replace( / /g, '_' ) );
var desc = ( article.extract || ).replace( /<[^>]+>/g, ).slice( 0, 160 );
if ( desc.length === 160 ) desc = desc.replace( /\s\S+$/, ) + '…';
var badgeStyle = series.color ? ' style="background:' + series.color + '"' : ;
return '
';
}
function buildDotsAndNav( count ) {
var dots = '
';
for ( var i = 0; i < count; i++ ) {
var cls = 'bsw-hero-dot' + ( i === 0 ? ' bsw-active' : );
dots += '';
}
dots += ''; var nav = '
' +
'‹' + '›' +'
';
return dots + nav; }
function initHeroSlideshow( hero, slides ) {
var dots = hero.querySelectorAll( '.bsw-hero-dot' );
var current = 0;
var total = slides.length;
var timer;
function goTo( n ) {
slides[ current ].classList.remove( 'bsw-active' );
dots[ current ] && dots[ current ].classList.remove( 'bsw-active' );
current = ( n + total ) % total;
slides[ current ].classList.add( 'bsw-active' );
dots[ current ] && dots[ current ].classList.add( 'bsw-active' );
resetTimer();
}
function resetTimer() {
clearInterval( timer );
timer = setInterval( function () { goTo( current + 1 ); }, 6000 );
}
window.bswGoSlide = function ( n ) { goTo( n ); };
window.bswPrevSlide = function () { goTo( current - 1 ); };
window.bswNextSlide = function () { goTo( current + 1 ); };
/* Click on image area (overlay or bg) navigates to article */
slides.forEach( function ( slide ) {
[ '.bsw-slide-overlay', '.bsw-slide-bg' ].forEach( function ( sel ) {
var el = slide.querySelector( sel );
if ( !el ) return;
el.style.cursor = 'pointer';
el.addEventListener( 'click', function () {
var url = slide.getAttribute( 'data-url' );
if ( url ) window.location.href = url;
} );
} );
} );
dots.forEach( function ( dot, i ) {
dot.addEventListener( 'click', function () { goTo( i ); } );
} );
var prevBtn = document.getElementById( 'bsw-hero-prev' );
var nextBtn = document.getElementById( 'bsw-hero-next' );
if ( prevBtn ) prevBtn.addEventListener( 'click', function () { goTo( current - 1 ); } );
if ( nextBtn ) nextBtn.addEventListener( 'click', function () { goTo( current + 1 ); } );
hero.addEventListener( 'mouseenter', function () { clearInterval( timer ); } );
hero.addEventListener( 'mouseleave', resetTimer );
resetTimer(); }
function fetchSlideImages( hero, slides ) {
var titles = Array.from( slides ).map( function ( s ) { return s.dataset.article; } ).filter( Boolean );
if ( !titles.length ) return;
apiGet( {
action: 'query',
titles: titles.join( '|' ),
prop: 'pageimages',
piprop: 'thumbnail|original',
pithumbsize: '1200',
redirects: '1'
} ).then( function ( data ) {
var pages = data.query.pages;
var imageMap = {};
var normalized = {};
if ( data.query.normalized ) {
data.query.normalized.forEach( function ( n ) { normalized[ n.to ] = n.from; } );
}
var redirects = {};
if ( data.query.redirects ) {
data.query.redirects.forEach( function ( r ) { redirects[ r.to ] = r.from; } );
}
Object.values( pages ).forEach( function ( page ) {
if ( !page.title ) return;
var img = ( page.original && page.original.source ) ||
( page.thumbnail && page.thumbnail.source ) || ;
if ( !img ) return;
imageMap[ page.title ] = img;
if ( redirects[ page.title ] ) imageMap[ redirects[ page.title ] ] = img;
if ( normalized[ page.title ] ) imageMap[ normalized[ page.title ] ] = img;
} );
slides.forEach( function ( slide ) {
var imgUrl = imageMap[ slide.dataset.article ];
if ( !imgUrl ) return;
var bg = slide.querySelector( '.bsw-slide-bg' );
if ( !bg ) return;
var bgImg = document.createElement( 'img' );
bgImg.src = imgUrl;
bgImg.alt = ;
bgImg.className = 'bsw-slide-bg-blur';
bgImg.setAttribute( 'aria-hidden', 'true' );
var fgImg = document.createElement( 'img' );
fgImg.src = imgUrl;
fgImg.alt = ;
fgImg.className = 'bsw-slide-bg-img';
bg.appendChild( bgImg );
bg.appendChild( fgImg );
} );
} ).catch( function () {} );
}
function initHero() {
var hero = document.querySelector( '.bsw-hero' );
if ( !hero ) return;
var seed = dailySeed();
var SLIDE_COUNT = 3;
var FETCH_BATCH = 20; /* MW caps rnlimit at 20 for non-bots */
/* Fetch two batches of random articles for a larger candidate pool */
Promise.all( [
apiGet( { action: 'query', list: 'random', rnnamespace: '0', rnlimit: String( FETCH_BATCH ) } ),
apiGet( { action: 'query', list: 'random', rnnamespace: '0', rnlimit: String( FETCH_BATCH ) } )
] ).then( function ( results ) {
var combined = results[0].query.random.concat( results[1].query.random );
/* Deduplicate and filter */
var seen = {};
var items = combined.filter( function ( p ) {
if ( seen[ p.title ] ) return false;
seen[ p.title ] = true;
return p.title !== 'Main Page' &&
p.title !== 'Main Page/New' &&
p.title.indexOf( '/' ) === -1;
} );
if ( !items.length ) throw new Error( 'No articles' );
/* Cap at 20 titles to stay within MW API limits */
items = items.slice( 0, 20 );
/* Fetch extracts + images + categories for all candidates in one call */
return apiGet( {
action: 'query',
titles: items.map( function ( p ) { return p.title; } ).join( '|' ),
prop: 'extracts|pageimages|categories|pageprops',
exintro: '1',
exchars: '200',
exsectionformat: 'plain',
piprop: 'thumbnail|original',
pithumbsize: '1200',
cllimit: '500',
ppprop: 'disambiguation',
redirects: '1'
} );
} ).then( function ( data ) {
var pages = Object.values( data.query.pages || {} );
/* Keep only articles that have an image and aren't disambiguation pages */
var withImages = pages.filter( function ( p ) {
if ( !p.thumbnail && !p.original ) return false;
var cats = ( p.categories || [] ).map( function ( c ) { return c.title; } );
var isDisamb = ( p.pageprops && p.pageprops.disambiguation !== undefined ) ||
cats.some( function ( c ) { return c === 'Category:Disambiguation'; } );
var isStub = cats.some( function ( c ) { return /[Ss]tub/.test( c ); } );
return !isDisamb && !isStub;
} );
/* Fall back to all non-disambiguation pages if not enough have images */
var pool = withImages.length >= SLIDE_COUNT ? withImages : pages.filter( function ( p ) {
var cats = ( p.categories || [] ).map( function ( c ) { return c.title; } );
var isDisamb = ( p.pageprops && p.pageprops.disambiguation !== undefined ) ||
cats.some( function ( c ) { return c === 'Category:Disambiguation'; } );
var isStub = cats.some( function ( c ) { return /[Ss]tub/.test( c ); } );
return !isDisamb && !isStub;
} );
if ( pool.length < SLIDE_COUNT ) throw new Error( 'Not enough articles' );
/* Pick SLIDE_COUNT articles using daily seed with spacing */
var picks = [];
for ( var i = 0; i < SLIDE_COUNT; i++ ) {
picks.push( pool[ ( seed + i * 7 ) % pool.length ] );
}
/* Build slide HTML */
var slideHTML = picks.map( function ( article, i ) {
var cats = article.categories || [];
var series = classifyArticle( article.title, cats, article.extract );
var html = buildSlideHTML( article, series, );
return i === 0 ? html : html.replace( 'bsw-slide bsw-active', 'bsw-slide' );
} ).join( );
hero.innerHTML = slideHTML + buildDotsAndNav( picks.length );
var newSlides = hero.querySelectorAll( '.bsw-slide' );
initHeroSlideshow( hero, newSlides );
/* Inject images directly from the data we already fetched */
picks.forEach( function ( article, i ) {
var imgUrl = ( article.original && article.original.source ) ||
( article.thumbnail && article.thumbnail.source ) || ;
if ( !imgUrl ) return;
var bg = newSlides[ i ] && newSlides[ i ].querySelector( '.bsw-slide-bg' );
if ( !bg ) return;
var bgImg = document.createElement( 'img' );
bgImg.src = imgUrl;
bgImg.alt = ;
bgImg.className = 'bsw-slide-bg-blur';
bgImg.setAttribute( 'aria-hidden', 'true' );
var fgImg = document.createElement( 'img' );
fgImg.src = imgUrl;
fgImg.alt = ;
fgImg.className = 'bsw-slide-bg-img';
bg.appendChild( bgImg );
bg.appendChild( fgImg );
} );
} ).catch( function ( err ) {
/* Fallback: use static Lua-rendered slides */
var staticSlides = hero.querySelectorAll( '.bsw-slide' );
if ( staticSlides.length ) {
/* Ensure first slide is active */
staticSlides.forEach( function( s ) { s.classList.remove( 'bsw-active' ); } );
staticSlides[0].classList.add( 'bsw-active' );
initHeroSlideshow( hero, staticSlides );
fetchSlideImages( hero, staticSlides );
}
} );
}
/* ════════════════════════════════════════════════════════════════
2. FEATURED ARTICLE OF THE DAY
Daily deterministic pick from all mainspace articles,
excluding Category:Stub_Pages.
════════════════════════════════════════════════════════════════ */
function initFeatured() {
var container = document.getElementById( 'bsw-featured-inner' );
if ( !container ) return;
var seed = dailySeed();
/* We fetch batches of 20 random articles, check for stubs,
and pick the first clean one. Two batches covers ~99.99%
of cases given only ~9% of articles are stubs. */
fetchCandidateBatch( seed )
.then( function ( pages ) {
var clean = pages.filter( function ( p ) {
/* If categories array is present and contains Stub_Pages,
exclude. Absence of categories key means not a stub. */
if ( !p.categories ) return true;
return !p.categories.some( function ( c ) {
return c.title === 'Category:Stub_Pages';
} );
} );
if ( !clean.length ) {
/* Extremely unlikely — fall back to second batch */
return fetchCandidateBatch( seed + 1 );
}
return clean;
} )
.then( function ( pages ) {
/* Pick deterministically from the clean pool */
var pick = pages[ seed % pages.length ];
renderFeatured( container, pick );
} )
.catch( function ( e ) {
container.innerHTML =
'
Could not load featured article. ' + esc( e.message ) + '
';
} ); }
/**
* Fetch 20 random mainspace articles with extract, thumbnail,
* and stub-category membership check in a single API call.
* Uses rnstart derived from seed for determinism.
*/
function fetchCandidateBatch( seed ) {
/* Step 1: get 20 random page IDs */
return apiGet( {
action: 'query',
list: 'random',
rnnamespace: '0',
rnlimit: '20'
} ).then( function ( data ) {
var ids = data.query.random.map( function ( p ) { return p.id; } ).join( '|' );
/* Step 2: batch fetch extract + thumbnail + stub check */
return apiGet( {
action: 'query',
pageids: ids,
prop: 'extracts|pageimages|categories',
exintro: '1',
exchars: '600',
exsectionformat: 'plain',
piprop: 'thumbnail',
pithumbsize: '320',
clcategories: 'Category:Stub_Pages',
cllimit: '1'
} );
} ).then( function ( data ) {
return Object.values( data.query.pages );
} );
}
function renderFeatured( container, page ) {
var title = page.title || ;
var url = 'https://en.battlestarwiki.org/wiki/' + encodeURIComponent( title.replace( / /g, '_' ) );
var extract = page.extract || ;
var thumb = page.thumbnail ? page.thumbnail.source : ;
var thumbHtml;
if ( thumb ) {
thumbHtml = '<img class="bsw-fa-thumb" src="' + esc( thumb ) + '" alt="' + esc( title ) + '" width="90" height="110">';
} else {
thumbHtml = '<img class="bsw-fa-thumb" src="https://en.battlestarwiki.org/resources/BSGWIKILOGO.png" alt="Battlestar Wiki" width="90" height="110">';
}
container.innerHTML =
thumbHtml +
'
' +
'
' +
' +
''<a href="' + esc( url ) + '">' + esc( title ) + '</a>' +'
' + extract + '
' +
'' +
' +
''<a href="' + esc( url ) + '" style="font-size:0.75rem;color:var(--color-link)">Read more \u2192</a>' +'
';
/* Update the "Read more" span in the card header to link to the article */
var hdrLink = document.getElementById( 'bsw-featured-link' );
if ( hdrLink ) {
/* Replace the span with a proper anchor now that we have a URL */
var a = document.createElement( 'a' );
a.href = url;
a.textContent = 'Read more \u2192';
hdrLink.parentNode.replaceChild( a, hdrLink );
}
}
/**
* Fetch file count from media.battlestarwiki.org and populate
* the #bsw-stat-files span in the statistics card.
*/
function initMediaFileCount() {
var el = document.getElementById( 'bsw-stat-files' );
if ( !el ) return;
apiGetFrom( MEDIA_API, {
action: 'query',
meta: 'siteinfo',
siprop: 'statistics'
} ).then( function ( data ) {
var files = data.query.statistics.images || 0;
el.textContent = files.toLocaleString();
} ).catch( function () {
el.textContent = '—';
} );
}
/**
* Recent changes tab switching.
* Fetches live recent changes from the selected wiki's API
* and replaces the #bsw-rc-content div content.
*/
var rcCurrentWiki = 'all';
var rcCache = {};
function loadRecentChanges( wiki ) {
var wrap = document.getElementById( 'bsw-rc-content' );
if ( !wrap ) return;
if ( rcCache[ wiki ] ) {
wrap.innerHTML = rcCache[ wiki ];
return;
}
/* 'all' shows the cached Special:RecentChanges transclude */
if ( wiki === 'all' ) {
if ( rcCache.all ) {
wrap.innerHTML = rcCache.all;
}
return;
}
wrap.innerHTML = '
Loading\u2026
';
var apiMap = { en: API, de: DE_API, fr: FR_API, media: MEDIA_API };
var base = apiMap[ wiki ];
if ( !base ) return;
var nsMap = {
en: '0',
de: '0',
fr: '0',
media: '0|6'
};
apiGetFrom( base, {
action: 'query',
list: 'recentchanges',
rcprop: 'title|timestamp|user|sizes',
rcnamespace: nsMap[ wiki ] || '0',
rclimit: '10',
rctype: 'edit|new'
} ).then( function ( data ) {
var changes = ( data.query.recentchanges || [] ).filter( function ( rc ) {
/* Filter out main page and subpages */
return rc.title !== 'Main Page' && rc.title.indexOf( '/' ) === -1;
} );
changes = changes.filter( function ( rc ) {
return rc.title !== 'Main Page' && rc.title.indexOf( '/' ) === -1;
} );
if ( !changes.length ) {
wrap.innerHTML = '
No recent changes.
';
return;
}
var baseUrl = {
en: 'https://en.battlestarwiki.org/',
de: 'https://de.battlestarwiki.org/',
fr: 'https://fr.battlestarwiki.ddns.net/wiki/',
media: 'https://media.battlestarwiki.org/'
}[ wiki ];
var isCrosswiki = ( wiki !== 'all' && wiki !== 'en' );
/* Deduplicate by title */
var seen = {};
changes = changes.filter( function ( rc ) {
if ( seen[ rc.title ] ) return false;
seen[ rc.title ] = true;
return true;
} );
var html = '
- ' + changes.map( function ( rc ) {
var title = rc.title;
var displayTitle = title.replace( /^[^:]+:/, ); /* strip namespace prefix */
var url = baseUrl + encodeURIComponent( title.replace( / /g, '_' ) );
var ts = new Date( rc.timestamp );
var ago = timeAgo( ts );
var cls = isCrosswiki ? ' class="external"' : ;
var target = isCrosswiki ? ' target="_blank"' : ;
return '
- ' + '<a href="' + esc( url ) + '"' + cls + target + '>' + esc( displayTitle ) + '</a>' + '' + esc( ago ) + '' + ' '; } ).join( ) + '
';
rcCache[ wiki ] = html;
wrap.innerHTML = html;
} ).catch( function () {
wrap.innerHTML = '
Could not load recent changes.
';
} ); }
function timeAgo( date ) {
var diff = Math.floor( ( Date.now() - date.getTime() ) / 1000 );
if ( diff < 60 ) return diff + 's ago';
if ( diff < 3600 ) return Math.floor( diff / 60 ) + ' min ago';
var hrs = Math.floor( diff / 3600 );
if ( diff < 86400 ) return hrs + ( hrs === 1 ? ' hr ago' : ' hrs ago' );
var days = Math.floor( diff / 86400 );
return days + ( days === 1 ? ' day ago' : ' days ago' );
}
/**
* Newest article — pulls from Special:NewPages via API,
* skips subpages, redirects, and disambiguation pages.
*/
function initNewestArticle() {
var inner = document.getElementById( 'bsw-newest-inner' );
if ( !inner ) return;
apiGet( {
action: 'query',
list: 'logevents',
letype: 'create',
lenamespace: '0',
lelimit: '20',
leprop: 'title|timestamp'
} ).then( function ( data ) {
var entries = ( data.query.logevents || [] ).filter( function ( e ) {
return e.title.indexOf( '/' ) === -1 && e.title !== 'Main Page';
} );
if ( !entries.length ) throw new Error( 'No entries' );
var entry = entries[0];
var title = entry.title;
var url = 'https://en.battlestarwiki.org/wiki/' + encodeURIComponent( title.replace( / /g, '_' ) );
var ts = new Date( entry.timestamp );
var date = ts.toLocaleDateString( 'en-US', { year: 'numeric', month: 'long', day: 'numeric' } );
return apiGet( {
action: 'query',
titles: title,
prop: 'extracts|pageimages|pageprops',
exintro: '1',
exchars: '500',
exsectionformat: 'plain',
piprop: 'thumbnail',
pithumbsize: '200',
ppprop: 'disambiguation'
} ).then( function ( d ) {
var page = Object.values( d.query.pages || {} )[0] || {};
/* Skip disambiguation pages, try next */
if ( page.pageprops && page.pageprops.disambiguation !== undefined ) {
var next = entries[1];
if ( !next ) throw new Error( 'No suitable page' );
title = next.title;
url = 'https://en.battlestarwiki.org/wiki/' + encodeURIComponent( title.replace( / /g, '_' ) );
date = new Date( next.timestamp ).toLocaleDateString( 'en-US', { year: 'numeric', month: 'long', day: 'numeric' } );
return apiGet( {
action: 'query', titles: title,
prop: 'extracts|pageimages', exintro: '1', exchars: '300',
exsectionformat: 'plain', piprop: 'thumbnail', pithumbsize: '200'
} ).then( function ( d2 ) {
return { page: Object.values( d2.query.pages || {} )[0] || {}, title: title, url: url, date: date };
} );
}
return { page: page, title: title, url: url, date: date };
} );
} ).then( function ( result ) {
var page = result.page;
var title = result.title || page.title || ;
var url = result.url || ( 'https://en.battlestarwiki.org/wiki/' + encodeURIComponent( ( page.title || ).replace( / /g, '_' ) ) );
var thumb = page.thumbnail ? page.thumbnail.source : ;
var extract = page.extract || ;
var thumbHtml = thumb
? '<img src="' + esc( thumb ) + '" alt="' + esc( title ) + '" class="bsw-fa-thumb" width="90" height="110">'
: '<img src="https://en.battlestarwiki.org/resources/BSGWIKILOGO.png" alt="Battlestar Wiki" class="bsw-fa-thumb" width="90" height="110">';
/* Clean up extract — truncate at sentence boundary */
var cleanExtract = extract.replace( /\s*\.\.\.\s*$/, );
/* Try to truncate at last sentence end within ~400 chars */
var stripped = cleanExtract.replace( /<[^>]+>/g, );
if ( stripped.length > 400 ) {
var lastPeriod = cleanExtract.lastIndexOf( '.', 500 );
if ( lastPeriod > 200 ) {
cleanExtract = cleanExtract.slice( 0, lastPeriod + 1 );
}
}
inner.innerHTML =
thumbHtml +
'
' +
'
<a href="' + esc( url ) + '">' + esc( title ) + '</a>
' +
'Created ' + esc( result.date ) + '
' +
'' + cleanExtract + '
' +
'' +
' +
''<a href="' + esc( url ) + '" style="font-size:0.75rem;color:var(--color-link)">Read more \u2192</a>' +'
';
} ).catch( function ( err ) {
inner.innerHTML = '
Could not load newest article.
';
} ); }
/* ── Entry point ──────────────────────────────────────────────── */
/**
* Hide any .bsw-card whose .bsw-card-body contains nothing but
* a red link (missing page transclusion). This catches subpages
* like BattlestarWiki:Did_you_know, OTD/Month_Day etc. that
* haven't been created yet.
*/
function filterRedlinkCards() {
document.querySelectorAll( '.bsw-main-page .bsw-card' ).forEach( function ( card ) {
/* Skip cards that are JS-populated — they start with a loading spinner
and will be filled in asynchronously */
if ( card.querySelector( '.bsw-loading, .bsw-spinner' ) ) return;
var body = card.querySelector( '.bsw-card-body' );
if ( !body ) return;
/* Remove non-content elements before checking — date labels,
loading spinners etc. shouldn't count as "content" */
var clone = body.cloneNode( true );
/* Remove known non-content wrappers */
clone.querySelectorAll( '.bsw-otd-date, .bsw-spinner, .bsw-loading' )
.forEach( function ( el ) { el.remove(); } );
var text = clone.textContent.trim();
var links = clone.querySelectorAll( 'a' );
var redLinks = clone.querySelectorAll( 'a.new' );
/* Hide if nothing left after stripping non-content */
if ( text === ) {
card.style.display = 'none';
return;
}
/* Hide if the only remaining content is red links */
if ( redLinks.length > 0 && redLinks.length === links.length ) {
clone.querySelectorAll( 'a' ).forEach( function ( a ) { a.remove(); } );
if ( clone.textContent.trim() === ) {
card.style.display = 'none';
}
}
} );
}
/* bswSetTab — exposed globally as fallback for inline onclick */
window.bswSetTab = function ( el, wiki ) {
document.querySelectorAll( '.bsw-wtab' ).forEach( function ( t ) {
t.classList.remove( 'bsw-active' );
} );
el.classList.add( 'bsw-active' );
rcCurrentWiki = wiki;
loadRecentChanges( wiki );
};
function wireRcTabs() {
var tabMap = { 'All': 'all', 'EN': 'en', 'DE': 'de', 'FR': 'fr', 'Media': 'media' };
document.querySelectorAll( '.bsw-wtab' ).forEach( function ( tab ) {
var wiki = tabMap[ tab.textContent.trim() ];
if ( !wiki ) return;
tab.addEventListener( 'click', function () {
document.querySelectorAll( '.bsw-wtab' ).forEach( function ( t ) {
t.classList.remove( 'bsw-active' );
} );
tab.classList.add( 'bsw-active' );
rcCurrentWiki = wiki;
loadRecentChanges( wiki );
} );
} );
}
mw.hook( 'wikipage.content' ).add( function () {
if ( !document.querySelector( '.bsw-main-page' ) ) return;
initHero();
initFeatured();
initMediaFileCount();
initNewestArticle();
filterRedlinkCards();
wireRcTabs();
/* Cache the initial 'all' RC content so tab switching can restore it */
var rcWrap = document.getElementById( 'bsw-rc-content' );
if ( rcWrap ) rcCache.all = rcWrap.innerHTML;
} );
}() );