Main Page: Difference between revisions

No edit summary
No edit summary
Line 1,117: Line 1,117:


<section class="vu-people" id="vu-people-slider" aria-label="People slideshow">
<section class="vu-people" id="vu-people-slider" aria-label="People slideshow">
<div class="vu-people-status" id="vu-people-status">Loading biographies…</div>
<div class="vu-people-status" id="vu-people-status" hidden></div>


<div class="vu-people-stage">
<div class="vu-people-stage">
Line 1,139: Line 1,139:
id="vu-person-link"
id="vu-person-link"
href="/wiki/Toonio_Noord">
href="/wiki/Toonio_Noord">
Open biography
Open page
</a>
</a>
</div>
</div>
Line 1,233: Line 1,233:
"use strict";
"use strict";


function ready(callback) {
function onReady(callback) {
if (document.readyState === "loading") {
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", callback);
document.addEventListener("DOMContentLoaded", callback, { once: true });
} else {
} else {
callback();
callback();
Line 1,241: Line 1,241:
}
}


ready(function () {
onReady(function () {
var root = document.querySelector(".vu-main");
var root = document.querySelector(".vu-main");
if (!root || !window.fetch || !window.DOMParser) return;
var fallbackImage = "/wiki/Special:Redirect/file/Vu-logo.png";
var slider = document.getElementById("vu-people-slider");
var slider = document.getElementById("vu-people-slider");
var status = document.getElementById("vu-people-status");
var image = document.getElementById("vu-person-image");
var image = document.getElementById("vu-person-image");
var title = document.getElementById("vu-person-title");
var title = document.getElementById("vu-person-title");
var extract = document.getElementById("vu-person-extract");
var extract = document.getElementById("vu-person-extract");
var link = document.getElementById("vu-person-link");
var pageLink = document.getElementById("vu-person-link");
var count = document.getElementById("vu-person-count");
var count = document.getElementById("vu-person-count");
var previous = document.getElementById("vu-person-prev");
var previous = document.getElementById("vu-person-prev");
var next = document.getElementById("vu-person-next");
var next = document.getElementById("vu-person-next");
var toggle = document.getElementById("vu-person-toggle");
var toggle = document.getElementById("vu-person-toggle");
if (!slider || !status || !image || !title || !extract || !link || !count || !previous || !next || !toggle) return;


function wikiUrl(name) {
if (
if (window.mw && mw.util) return mw.util.getUrl(name);
!root ||
return "/wiki/" + encodeURIComponent(name.replace(/ /g, "_")).replace(/%2F/g, "/");
!slider ||
!image ||
!title ||
!extract ||
!pageLink ||
!count ||
!previous ||
!next ||
!toggle
) {
return;
}
}


function fetchText(url) {
var fallbackImage = "/wiki/Special:Redirect/file/Vu-logo.png";
return fetch(url, { credentials: "same-origin", cache: "no-store" }).then(function (response) {
var cacheKey = "vu-main-people-slides-v4";
if (!response.ok) throw new Error("Request failed");
var cacheLifetime = 6 * 60 * 60 * 1000;
return response.text();
var slideInterval = 9000;
});
var people = [];
}
var currentIndex = 0;
var timer = null;
var playing = false;
 
previous.disabled = true;
next.disabled = true;
toggle.disabled = true;
count.textContent = "1 / 1";
toggle.textContent = "Play";


function shuffle(items) {
function shuffle(items) {
for (var i = items.length - 1; i > 0; i--) {
var result = items.slice();
var j = Math.floor(Math.random() * (i + 1));
 
var temporary = items[i];
for (var index = result.length - 1; index > 0; index--) {
items[i] = items[j];
var randomIndex = Math.floor(Math.random() * (index + 1));
items[j] = temporary;
var temporary = result[index];
result[index] = result[randomIndex];
result[randomIndex] = temporary;
}
}
return items;
}


function unique(items) {
return result;
var seen = {};
return items.filter(function (item) {
if (!item || !item.title || seen[item.title]) return false;
seen[item.title] = true;
return true;
});
}
}


function markMissingLinks() {
function cleanText(value) {
if (!window.mw || !mw.loader) return;
return String(value || "")
mw.loader.using(["mediawiki.api", "mediawiki.util"]).then(function () {
.replace(/\[\s*\d+(?:\.\d+)?\s*\]/g, "")
var api = new mw.Api();
.replace(/\s+/g, " ")
var links = Array.prototype.slice.call(root.querySelectorAll("a[data-wiki-title]"));
.trim();
var titles = links.map(function (item) { return item.getAttribute("data-wiki-title"); })
.filter(function (item, index, all) { return item && all.indexOf(item) === index; });
for (var i = 0; i < titles.length; i += 50) {
(function (batch) {
api.get({ action: "query", formatversion: 2, titles: batch.join("|") }).then(function (data) {
var missing = {};
if (data.query && data.query.pages) {
data.query.pages.forEach(function (page) { if (page.missing) missing[page.title] = true; });
}
links.forEach(function (item) {
var pageTitle = item.getAttribute("data-wiki-title");
if (!missing[pageTitle]) return;
item.classList.add("new");
item.href = mw.util.getUrl(pageTitle, { action: "edit", redlink: 1 });
item.title = pageTitle + " (page does not exist)";
});
}).catch(function () {});
})(titles.slice(i, i + 50));
}
}).catch(function () {});
}
}


markMissingLinks();
function shorten(value, limit) {
var cleaned = cleanText(value);


var fallbackTitles = [
if (!cleaned || cleaned.length <= limit) {
"Abdelkrim El Berkani", "Alessandro Nostrini", "Alexandru Ionuț", "Ana-Maria Nistor",
return cleaned;
"Andrei Ionuț", "Angela Noord", "Angelo Angedrik Noord", "Angelo Martin Zuid",
}
"Antonie Ronald Paap", "Ben Paap", "Benny Paap", "Bob Noord", "Cees Paap",
"Charlie Churchill", "Derek Paap", "Domenico Nostrini", "Eef Hoos", "Eef Paap",
"Emiel Noord", "Eva Noord", "Evert Angedrik Noord", "Francesco Nostrini",
"Girolamo Nostrini", "Grozav Ionuț", "Humphrey van Hetten", "Jan Paap",
"Kateryna Moroz", "Kenzi Schladenberg", "Lorenzo Nostrini", "Lourens Schroeter",
"Martin Noord", "Matteo Nostrini", "Nicolae Ionuț", "Otto Hoos", "Rasmus Rogganoid",
"Richard Hoos", "Rikard Rogganoid", "Toonio Noord", "Viorica Rogganoid", "Walter Noord"
];


function fallbackMembers() {
var cut = cleaned.slice(0, limit);
return fallbackTitles.map(function (name) { return { title: name, url: wikiUrl(name) }; });
var sentenceEnd = Math.max(
}
cut.lastIndexOf(". "),
cut.lastIndexOf("! "),
cut.lastIndexOf("? ")
);


function parseCategory(html) {
if (sentenceEnd > 180) {
var doc = new DOMParser().parseFromString(html, "text/html");
return cut.slice(0, sentenceEnd + 1);
var area = doc.querySelector("#mw-pages") || doc.querySelector(".mw-category");
}
if (!area) return [];
var anchors = Array.prototype.slice.call(area.querySelectorAll(".mw-category-group a, .mw-category a"));
return unique(anchors.map(function (anchor) {
var name = (anchor.textContent || "").trim();
var href = anchor.getAttribute("href") || "";
if (!name || !href || /^(Category|Special|File|Template|Help|Talk|User|MediaWiki):/i.test(name)) return null;
return { title: name, url: new URL(href, window.location.href).href };
}));
}


function getMembers() {
var finalSpace = cut.lastIndexOf(" ");
return fetchText(wikiUrl("Category:People")).then(function (html) {
return cut.slice(0, finalSpace > 0 ? finalSpace : limit) + "…";
var members = parseCategory(html);
if (members.length < 2) throw new Error("No category members");
return members;
}).catch(function () { return fallbackMembers(); });
}
}


function clean(text) {
function getImage(page) {
return (text || "").replace(/\[\s*\d+(?:\.\d+)?\s*\]/g, "").replace(/\s+/g, " ").trim();
if (page.thumbnail && page.thumbnail.source) {
}
return page.thumbnail.source;
}


function shorten(text, limit) {
if (page.original && page.original.source) {
var value = clean(text);
return page.original.source;
if (value.length <= limit) return value;
}
var cut = value.slice(0, limit);
var stop = Math.max(cut.lastIndexOf(". "), cut.lastIndexOf("! "), cut.lastIndexOf("? "));
if (stop > 180) return cut.slice(0, stop + 1);
var space = cut.lastIndexOf(" ");
return cut.slice(0, space > 0 ? space : limit) + "…";
}


function findLead(content) {
if (!content) return "";
var paragraphs = Array.prototype.slice.call(content.children).filter(function (element) { return element.tagName === "P"; });
paragraphs = paragraphs.concat(Array.prototype.slice.call(content.querySelectorAll("p")));
for (var i = 0; i < paragraphs.length; i++) {
var paragraph = paragraphs[i];
if (paragraph.closest("table") || paragraph.closest(".navbox") || paragraph.closest(".mw-references-wrap")) continue;
var value = clean(paragraph.textContent);
if (value.length >= 70) return value;
}
return "";
return "";
}
}


function scoreImage(element, pageTitle) {
function normalizePages(data) {
var source = element.getAttribute("src") || element.getAttribute("data-src") || "";
var pages =
var alt = ((element.getAttribute("alt") || "") + " " + (element.getAttribute("title") || "")).toLowerCase();
data &&
var combined = (source + " " + alt).toLowerCase();
data.query &&
if (!source || /(logo|icon|button|spinner|poweredby|powered_by|wikimedia-button|ambox|question_book|edit-clear|magnify-clip)/i.test(combined)) return -1000;
Array.isArray(data.query.pages)
var score = 0;
? data.query.pages
if (/(signature|coat of arms|flag of|map of|location map)/i.test(alt)) score -= 8;
: [];
if (alt.indexOf(pageTitle.toLowerCase()) !== -1) score += 12;
 
if (element.closest(".infobox")) score += 10;
return shuffle(
if (element.closest("table")) score += 4;
pages
var width = parseInt(element.getAttribute("width"), 10) || 0;
.filter(function (page) {
var height = parseInt(element.getAttribute("height"), 10) || 0;
return (
if (width >= 150 || height >= 180) score += 3;
page &&
return score;
!page.missing &&
page.title &&
cleanText(page.extract).length >= 60 &&
getImage(page)
);
})
.map(function (page) {
return {
title: page.title,
extract: shorten(page.extract, 650),
image: getImage(page),
url:
page.fullurl ||
(window.mw && mw.util
? mw.util.getUrl(page.title)
: "/wiki/" +
  encodeURIComponent(
  page.title.replace(/ /g, "_")
  ))
};
})
).slice(0, 12);
}
}


function findImage(content, pageTitle, pageUrl) {
function readCache() {
if (!content) return "";
try {
var images = Array.prototype.slice.call(content.querySelectorAll(".infobox img, table img, figure img, .thumb img, img"));
var cached = JSON.parse(
var best = null;
window.sessionStorage.getItem(cacheKey) || "null"
var bestScore = -1000;
);
images.forEach(function (candidate) {
 
var score = scoreImage(candidate, pageTitle);
if (
if (score > bestScore) { bestScore = score; best = candidate; }
cached &&
});
cached.savedAt &&
if (!best || bestScore < 0) return "";
Date.now() - cached.savedAt < cacheLifetime &&
var source = best.getAttribute("src") || best.getAttribute("data-src") || "";
Array.isArray(cached.people) &&
try { return new URL(source, pageUrl).href; } catch (error) { return ""; }
cached.people.length > 1
}
) {
return cached.people;
}
} catch (error) {
return null;
}


function parsePerson(member, html) {
return null;
var doc = new DOMParser().parseFromString(html, "text/html");
var content = doc.querySelector("#mw-content-text .mw-parser-output") || doc.querySelector(".mw-parser-output");
var heading = doc.querySelector("#firstHeading");
var pageTitle = heading ? heading.textContent.trim() : member.title;
var lead = findLead(content);
var pageImage = findImage(content, pageTitle, member.url);
if (!lead || !pageImage) return null;
return { title: pageTitle, extract: shorten(lead, 660), image: pageImage, url: member.url };
}
}


function loadPerson(member) {
function saveCache(items) {
return fetchText(member.url).then(function (html) { return parsePerson(member, html); }).catch(function () { return null; });
try {
window.sessionStorage.setItem(
cacheKey,
JSON.stringify({
savedAt: Date.now(),
people: items
})
);
} catch (error) {
return;
}
}
}


function loadPeople(members, desired) {
function preloadNext() {
var candidates = shuffle(unique(members).slice()).slice(0, 60);
if (people.length < 2) {
var results = [];
return;
var cursor = 0;
function worker() {
if (cursor >= candidates.length || results.length >= desired) return Promise.resolve();
var member = candidates[cursor++];
return loadPerson(member).then(function (person) {
if (person && !results.some(function (existing) { return existing.title === person.title; })) results.push(person);
}).then(worker);
}
}
var workers = [];
 
for (var i = 0; i < 6; i++) workers.push(worker());
var nextIndex = (currentIndex + 1) % people.length;
return Promise.all(workers).then(function () { return results.slice(0, desired); });
var preload = new Image();
preload.src = people[nextIndex].image;
}
}
var people = [];
var current = 0;
var timer = null;
var playing = true;
var interval = 9000;


function restartProgress() {
function restartProgress() {
slider.classList.remove("is-playing");
slider.classList.remove("is-playing");
void slider.offsetWidth;
void slider.offsetWidth;
if (playing && people.length > 1) slider.classList.add("is-playing");
 
if (playing && people.length > 1) {
slider.classList.add("is-playing");
}
}
}


function schedule() {
function scheduleNext() {
if (timer) window.clearTimeout(timer);
if (timer) {
window.clearTimeout(timer);
}
 
restartProgress();
restartProgress();
if (!playing || people.length < 2) return;
 
timer = window.setTimeout(function () { show(current + 1); }, interval);
if (!playing || people.length < 2) {
return;
}
 
timer = window.setTimeout(function () {
showSlide(currentIndex + 1);
}, slideInterval);
}
}


function show(index) {
function showSlide(index) {
if (!people.length) return;
if (!people.length) {
current = (index + people.length) % people.length;
return;
var person = people[current];
}
 
currentIndex = (index + people.length) % people.length;
var person = people[currentIndex];
 
slider.classList.add("is-changing");
slider.classList.add("is-changing");
window.setTimeout(function () {
window.setTimeout(function () {
image.src = person.image || fallbackImage;
image.src = person.image || fallbackImage;
Line 1,472: Line 1,460:
title.textContent = person.title;
title.textContent = person.title;
extract.textContent = person.extract;
extract.textContent = person.extract;
link.href = person.url;
pageLink.href = person.url;
count.textContent = (current + 1) + " / " + people.length;
pageLink.textContent = "Open page";
window.setTimeout(function () { slider.classList.remove("is-changing"); }, 40);
count.textContent =
}, 150);
String(currentIndex + 1) + " / " + String(people.length);
schedule();
 
window.setTimeout(function () {
slider.classList.remove("is-changing");
}, 40);
 
preloadNext();
}, 140);
 
scheduleNext();
}
}


function setPlaying(value) {
function setPlaying(value) {
playing = value && people.length > 1;
playing = Boolean(value && people.length > 1);
toggle.textContent = playing ? "Pause" : "Play";
toggle.textContent = playing ? "Pause" : "Play";
toggle.setAttribute("aria-label", playing ? "Pause slideshow" : "Play slideshow");
toggle.setAttribute(
if (playing) schedule();
"aria-label",
else {
playing ? "Pause slideshow" : "Play slideshow"
if (timer) window.clearTimeout(timer);
);
 
if (playing) {
scheduleNext();
} else {
if (timer) {
window.clearTimeout(timer);
}
 
slider.classList.remove("is-playing");
slider.classList.remove("is-playing");
}
}
}
}


previous.addEventListener("click", function () { show(current - 1); });
function activateSlides(items) {
next.addEventListener("click", function () { show(current + 1); });
if (!Array.isArray(items) || items.length < 2) {
toggle.addEventListener("click", function () { setPlaying(!playing); });
return;
}
 
people = shuffle(items);
currentIndex = 0;
previous.disabled = false;
next.disabled = false;
toggle.disabled = false;
showSlide(0);
setPlaying(true);
}
 
previous.addEventListener("click", function () {
showSlide(currentIndex - 1);
});
 
next.addEventListener("click", function () {
showSlide(currentIndex + 1);
});
 
toggle.addEventListener("click", function () {
setPlaying(!playing);
});
 
slider.addEventListener("mouseenter", function () {
slider.addEventListener("mouseenter", function () {
if (playing && timer) { window.clearTimeout(timer); slider.classList.remove("is-playing"); }
if (playing && timer) {
window.clearTimeout(timer);
slider.classList.remove("is-playing");
}
});
 
slider.addEventListener("mouseleave", function () {
if (playing) {
scheduleNext();
}
});
 
image.addEventListener("error", function () {
if (image.src.indexOf("Vu-logo.png") === -1) {
image.src = fallbackImage;
}
});
});
slider.addEventListener("mouseleave", function () { if (playing) schedule(); });
image.addEventListener("error", function () { if (image.src.indexOf("Vu-logo.png") === -1) image.src = fallbackImage; });


status.hidden = false;
var cachedPeople = readCache();
getMembers()
 
.then(function (members) { return loadPeople(members, 12); })
if (cachedPeople) {
.then(function (loaded) {
activateSlides(cachedPeople);
if (loaded.length >= 2) return loaded;
}
return loadPeople(fallbackMembers(), 12).then(function (fallbackLoaded) { return unique(loaded.concat(fallbackLoaded)); });
 
if (!window.mw || !mw.loader) {
return;
}
 
mw.loader
.using(["mediawiki.api", "mediawiki.util"])
.then(function () {
var api = new mw.Api();
 
/*
* One request obtains category members, lead extracts,
* page images and page URLs.
*/
return api.get({
action: "query",
generator: "categorymembers",
gcmtitle: "Category:People",
gcmnamespace: 0,
gcmtype: "page",
gcmlimit: 120,
prop: "extracts|pageimages|info",
exintro: 1,
explaintext: 1,
exchars: 700,
piprop: "thumbnail|original",
pithumbsize: 1000,
inprop: "url",
redirects: 1,
formatversion: 2
});
})
.then(function (data) {
var loadedPeople = normalizePages(data);
 
if (loadedPeople.length < 2) {
return;
}
 
saveCache(loadedPeople);
activateSlides(loadedPeople);
})
})
.then(function (loaded) {
.catch(function () {
status.hidden = true;
/*
if (!loaded.length) {
* The initial slide remains visible if the request fails.
count.textContent = "1 / 1";
* No permanent loading message is shown.
setPlaying(false);
*/
return;
});
 
/*
* Raw HTML links need a separate existence check so missing pages
* keep MediaWiki's red-link appearance.
*/
mw.loader
.using(["mediawiki.api", "mediawiki.util"])
.then(function () {
var api = new mw.Api();
var links = Array.prototype.slice.call(
root.querySelectorAll("a[data-wiki-title]")
);
 
var titles = links
.map(function (item) {
return item.getAttribute("data-wiki-title");
})
.filter(function (item, index, allItems) {
return item && allItems.indexOf(item) === index;
});
 
if (!titles.length) {
return;
return;
}
}
people = shuffle(loaded);
 
previous.disabled = people.length < 2;
return api
next.disabled = people.length < 2;
.get({
toggle.disabled = people.length < 2;
action: "query",
show(0);
titles: titles.join("|"),
setPlaying(people.length > 1);
formatversion: 2
})
.then(function (data) {
var missing = {};
 
if (data.query && Array.isArray(data.query.pages)) {
data.query.pages.forEach(function (page) {
if (page.missing) {
missing[page.title] = true;
}
});
}
 
links.forEach(function (item) {
var pageTitle =
item.getAttribute("data-wiki-title");
 
if (!missing[pageTitle]) {
return;
}
 
item.classList.add("new");
item.href = mw.util.getUrl(pageTitle, {
action: "edit",
redlink: 1
});
item.title =
pageTitle + " (page does not exist)";
});
});
})
})
.catch(function () {
.catch(function () {
status.hidden = true;
return;
count.textContent = "1 / 1";
previous.disabled = true;
next.disabled = true;
toggle.disabled = true;
setPlaying(false);
});
});
});
});