MediaWiki:Gadget-SidebarImagesMobile.js
Appearance
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/**
* SidebarImagesMobile
* - Minerva-only, mobile-only gadget to render social icon links at the bottom of the left drawer.
* - Intentionally does NOT touch #p-personal or any MinervaCustomizer-owned DOM.
* - Uses its own bottom container: #icw-minerva-social-icons
* - Keeps logic self-contained to avoid coupling with Vector gadget.
*
* Deployment note (in Gadgets-definition):
* SidebarImagesMobile[ResourceLoader|targets=mobile|skins=minerva|hidden|default]|JS/Gadgets/SidebarImagesMobile.js
*/
(function ($, mw) {
'use strict';
if (window.ICWSidebarImagesMobileLoaded) return;
window.ICWSidebarImagesMobileLoaded = true;
// Config is loaded dynamically from MediaWiki:SidebarImages.json at runtime.
// Fallback URL builder when API fails
function buildRedirectThumbURL(filename, size) {
const title = 'Special:Redirect/file/' + filename;
return mw.util.getUrl(title, { width: String(size) });
}
// Per-skin size selection
function pickSize(sizeCfg) {
if (typeof sizeCfg === 'number') return sizeCfg;
var skin = mw.config.get('skin');
return (sizeCfg && (sizeCfg[skin] || sizeCfg.default)) || 22;
}
// Load shared JSON config from on-wiki page (with fallback)
function getSidebarIconsConfig() {
if (window.ICWSidebarIconsConfigPromise) return window.ICWSidebarIconsConfigPromise;
window.ICWSidebarIconsConfigPromise = mw.loader.using('mediawiki.api').then(function () {
var api = new mw.Api();
return api.get({
action: 'query',
prop: 'revisions',
titles: 'MediaWiki:SidebarImages.json',
rvprop: 'content',
rvslots: '*',
formatversion: 2
}).then(function (data) {
var page = data && data.query && data.query.pages && data.query.pages[0];
var text = page && page.revisions && page.revisions[0] &&
(page.revisions[0].slots ? page.revisions[0].slots.main.content : page.revisions[0].content);
if (!text) throw new Error('No JSON content');
return JSON.parse(text);
}).catch(function () {
// Fallback: return null to trigger simple text fallback
return null;
});
});
return window.ICWSidebarIconsConfigPromise;
}
function fetchIconURLs(files, size) {
const api = new mw.Api({ timeout: 8000 });
return api.get({
action: 'query',
formatversion: 2,
prop: 'imageinfo',
titles: files,
iiprop: 'url|mime',
iiurlwidth: size
}).then(function (data) {
const out = Object.create(null);
const pages = (data && data.query && data.query.pages) || [];
for (const page of pages) {
if (!page || page.missing || !page.title) continue;
const baseName = page.title.replace(/^File:/i, '');
const info = page.imageinfo && page.imageinfo[0];
if (info && info.mime === 'image/svg+xml' && info.url) {
out[baseName] = info.url;
} else if (info && info.thumburl) {
out[baseName] = info.thumburl;
} else {
out[baseName] = buildRedirectThumbURL(baseName, size);
}
}
return out;
});
}
// Ensure a bottom container at the end of the left drawer
function ensureBottomContainer() {
const drawer = document.getElementById('mw-mf-page-left');
if (!drawer) return null;
let slot = document.getElementById('icw-minerva-social-icons');
if (!slot) {
slot = document.createElement('div');
slot.id = 'icw-minerva-social-icons';
// Mark for OpenExternal.js and CSS targeting
slot.className = 'icw-minerva-social-icons link-open-external';
// Append as last child to guarantee bottom-most placement
drawer.appendChild(slot);
}
return slot;
}
// Render one or more groups at the bottom slot
function renderGroupsAtBottom(groups, urlMap) {
const slot = ensureBottomContainer();
if (!slot) return;
// Idempotency: if already rendered, skip
if (slot.getAttribute('data-icw-icons') === '1') return;
// Clear and render each group as its own icon row
slot.innerHTML = '';
groups.forEach(function (group) {
if (!group || !Array.isArray(group.icons)) return;
const groupWrapper = document.createElement('div');
groupWrapper.className = 'sidebar-icon-group link-open-external';
const size = pickSize(group.size);
for (const icon of group.icons) {
const src = urlMap[icon.file];
if (!src) continue;
const a = document.createElement('a');
a.href = icon.url;
const img = document.createElement('img');
img.src = src;
img.alt = icon.name;
img.width = size;
img.height = size;
img.decoding = 'async';
img.loading = 'eager';
if (icon.className) {
img.classList.add(icon.className);
}
a.appendChild(img);
groupWrapper.appendChild(a);
}
slot.appendChild(groupWrapper);
});
slot.setAttribute('data-icw-icons', '1');
}
function insertTextFallback() {
const slot = ensureBottomContainer();
if (!slot) return;
// Idempotency: if already rendered, skip
if (slot.getAttribute('data-icw-icons') === '1') return;
const link = document.createElement('a');
link.href = '/Main_Page';
link.textContent = 'ICANNWiki';
slot.innerHTML = '';
slot.appendChild(link);
slot.setAttribute('data-icw-icons', '1');
}
function kick() {
getSidebarIconsConfig().then(function (cfg) {
const groups = (cfg && cfg.groups) || [];
if (!groups.length) {
insertTextFallback();
return;
}
const allIcons = groups.flatMap(g => g.icons);
const files = allIcons.map(i => 'File:' + i.file);
const maxSize = Math.max.apply(null, groups.map(g => pickSize(g.size)));
fetchIconURLs(files, maxSize)
.then(function (urlMap) {
renderGroupsAtBottom(groups, urlMap);
})
.catch(function (err) {
console.groupCollapsed('[SidebarImagesMobile] API error occurred.');
console.error(err);
console.groupEnd();
insertTextFallback();
});
});
}
$(function () {
if (mw.config.get('skin') !== 'minerva') return;
mw.loader.using(['mediawiki.api', 'mediawiki.util']).then(function () {
// Try immediately
if (document.getElementById('mw-mf-page-left')) {
kick();
}
// Observe for drawer creation/re-render; re-run idempotent
let throttle = null;
const obs = new MutationObserver(function () {
if (!document.getElementById('mw-mf-page-left')) return;
if (throttle) return;
throttle = setTimeout(function () {
throttle = null;
kick();
}, 250);
});
obs.observe(document.body, { childList: true, subtree: true });
});
});
})(jQuery, mediaWiki);