MediaWiki:Common.js: Difference between revisions
Jump to navigation
Jump to search
Created page with "→Any JavaScript here will be loaded for all users on every page load.: →Page previews: hovering a link in a page shows that page's infobox in a card.: (function () { const API = "/wiki/api.php"; const DELAY = 300; const OTHER = /^(Category|File|Help|MediaWiki|Special|Talk|Template|Titans of Time|User|Data):/; function start() { const content = document.getElementById("mw-content-text"); if (window.totPagePreviews === true || conte..." |
No edit summary |
||
| Line 1: | Line 1: | ||
/* Any JavaScript here will be loaded for all users on every page load. */ | |||
/* Any JavaScript here will be loaded for all users on every page load. */ | /* Any JavaScript here will be loaded for all users on every page load. */ | ||
/* Page previews: hovering a link in a page shows that page's infobox in a card. */ | /* Page previews: hovering a link in a page shows that page's infobox in a card. */ | ||
| Line 37: | Line 38: | ||
}; | }; | ||
const infobox = | const infobox = (title) => { | ||
if (known.has(title)) return known.get(title); | if (known.has(title)) return Promise.resolve(known.get(title)); | ||
const ask = `?action=parse&format=json&prop=text&disablelimitreport=1&page=${encodeURIComponent(`Data:${title}/Infobox`)}`; | const ask = `?action=parse&format=json&prop=text&disablelimitreport=1&page=${encodeURIComponent(`Data:${title}/Infobox`)}`; | ||
return fetch(API + ask).then((response) => response.json()).then((json) => { | |||
const holder = document.createElement("div"); | |||
holder.innerHTML = json.parse ? json.parse.text["*"] : ""; | |||
const table = holder.querySelector("table.wikitable"); | |||
if (table !== null) { | |||
table.style.cssText = "float:none; margin:0; width:100%; max-width:246px;"; | |||
// The page's own navigation row and an empty picture row say nothing in a card. | |||
table.querySelectorAll("tr").forEach((row) => { | |||
const empty = row.textContent.trim() === "" && row.querySelector("img") === null; | |||
if (empty || row.querySelector('a[title="Overview"]') !== null) row.remove(); | |||
}); | |||
table.querySelectorAll("img").forEach((image) => { | |||
image.removeAttribute("srcset"); | |||
image.removeAttribute("width"); | |||
image.removeAttribute("height"); | |||
image.style.cssText = "max-width:100%; max-height:170px; width:auto; height:auto;"; | |||
}); | |||
} | |||
known.set(title, table); | |||
return table; | |||
}); | |||
}; | }; | ||
| Line 101: | Line 103: | ||
card.style.display = "block"; | card.style.display = "block"; | ||
place(x, y); | place(x, y); | ||
// A picture still loading makes the card taller once it arrives, so place it again then. | |||
card.querySelectorAll("img").forEach((image) => { | |||
if (!image.complete) image.addEventListener("load", () => { | |||
if (shown === link) place(x, y); | |||
}, {once: true}); | |||
}); | |||
}).catch(() => undefined); | }).catch(() => undefined); | ||
}, DELAY); | }, DELAY); | ||
Latest revision as of 02:50, 2 November 2026
/* Any JavaScript here will be loaded for all users on every page load. */
/* Any JavaScript here will be loaded for all users on every page load. */
/* Page previews: hovering a link in a page shows that page's infobox in a card. */
(function () {
const API = "/wiki/api.php";
const DELAY = 300;
const OTHER = /^(Category|File|Help|MediaWiki|Special|Talk|Template|Titans of Time|User|Data):/;
function start() {
const content = document.getElementById("mw-content-text");
if (window.totPagePreviews === true || content === null || !window.matchMedia("(hover: hover)").matches) return;
window.totPagePreviews = true;
const known = new Map();
let timer;
let shown = null;
// The browser's own tooltip would cover the card, so the link gives up its title while hovered.
let titled = null;
let linkTitle = "";
const restoreTitle = () => {
if (titled !== null) titled.setAttribute("title", linkTitle);
titled = null;
};
const card = document.createElement("div");
card.style.cssText = "position:fixed; z-index:9999; display:none; pointer-events:none; max-width:260px; " +
"background:#fff; border:1px solid #a2a9b1; border-radius:6px; box-shadow:0 4px 16px rgba(0,0,0,0.3); padding:6px; font-size:13px;";
document.body.appendChild(card);
const titleOf = (link) => {
if (link.classList.contains("new") || link.classList.contains("selflink")) return null;
const url = new URL(link.href, document.baseURI);
const title = url.searchParams.get("title");
if (!/\/wiki\/index\.php$/.test(url.pathname) || title === null || url.searchParams.has("action")) return null;
const plain = title.replace(/_/g, " ");
return OTHER.test(plain) ? null : plain;
};
const infobox = (title) => {
if (known.has(title)) return Promise.resolve(known.get(title));
const ask = `?action=parse&format=json&prop=text&disablelimitreport=1&page=${encodeURIComponent(`Data:${title}/Infobox`)}`;
return fetch(API + ask).then((response) => response.json()).then((json) => {
const holder = document.createElement("div");
holder.innerHTML = json.parse ? json.parse.text["*"] : "";
const table = holder.querySelector("table.wikitable");
if (table !== null) {
table.style.cssText = "float:none; margin:0; width:100%; max-width:246px;";
// The page's own navigation row and an empty picture row say nothing in a card.
table.querySelectorAll("tr").forEach((row) => {
const empty = row.textContent.trim() === "" && row.querySelector("img") === null;
if (empty || row.querySelector('a[title="Overview"]') !== null) row.remove();
});
table.querySelectorAll("img").forEach((image) => {
image.removeAttribute("srcset");
image.removeAttribute("width");
image.removeAttribute("height");
image.style.cssText = "max-width:100%; max-height:170px; width:auto; height:auto;";
});
}
known.set(title, table);
return table;
});
};
const place = (x, y) => {
const box = card.getBoundingClientRect();
const left = x + 16 + box.width > window.innerWidth ? x - box.width - 16 : x + 16;
const top = Math.min(y + 16, window.innerHeight - box.height - 8);
card.style.left = `${Math.max(8, left)}px`;
card.style.top = `${Math.max(8, top)}px`;
};
const hide = () => {
clearTimeout(timer);
restoreTitle();
shown = null;
card.style.display = "none";
};
content.addEventListener("mouseover", (event) => {
const link = event.target.closest("a");
if (link === null || link === shown) return;
const title = titleOf(link);
if (title === null) return;
hide();
shown = link;
if (link.hasAttribute("title")) {
titled = link;
linkTitle = link.title;
link.removeAttribute("title");
}
const {clientX: x, clientY: y} = event;
timer = setTimeout(() => {
infobox(title).then((table) => {
if (shown !== link) return;
if (table === null) {
restoreTitle();
return;
}
card.textContent = "";
card.appendChild(table);
card.style.display = "block";
place(x, y);
// A picture still loading makes the card taller once it arrives, so place it again then.
card.querySelectorAll("img").forEach((image) => {
if (!image.complete) image.addEventListener("load", () => {
if (shown === link) place(x, y);
}, {once: true});
});
}).catch(() => undefined);
}, DELAY);
});
content.addEventListener("mouseout", (event) => {
const link = event.target.closest("a");
if (link !== null && !link.contains(event.relatedTarget)) hide();
});
window.addEventListener("scroll", hide, {passive: true});
}
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", start);
else start();
})();