Jump to content

MediaWiki:Gadget-SidebarImages.js

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.
// Enables the injection of images into sidebars
// IMPORTANT: Configuration is co-dependent with that of MediaWiki:Common.css
(function ($, mw) {
  'use strict';
  if (window.ICWSidebarImagesLoaded) return;
  window.ICWSidebarImagesLoaded = 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) });
  }

  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]) || null;
        // Priority: SVG direct URL, then thumbnail, then redirect fallback
        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;
    });
  }

  // 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)) || 24;
  }

  // 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 insertIconGroups(groups, urlMap) {
    for (const group of groups) {
      const container = document.getElementById(group.containerId);
      if (!container) continue;

      const groupWrapper = document.createElement('div');
      // Classes required for CSS styling and OpenExternal.js processing
      groupWrapper.className = 'sidebar-icon-group link-open-external';
      
      for (const icon of group.icons) {
        const src = urlMap[icon.file];
        if (!src) continue;

        const link = document.createElement('a');
        link.href = icon.url; 
        
        const img = document.createElement('img');
        img.src = src;
        img.alt = icon.name;
        img.width = pickSize(group.size);
        img.height = pickSize(group.size);
        img.decoding = 'async';
        img.loading = 'eager';

        if (icon.className) {
          img.classList.add(icon.className);
        }
        
        link.appendChild(img);
        groupWrapper.appendChild(link);
      }

      // Replace placeholder content and show icons
      container.innerHTML = '';
      container.appendChild(groupWrapper);
      container.style.opacity = 1;
      container.style.pointerEvents = 'auto';
    }
  }

  function insertTextFallback() {
    const container = document.getElementById('n-Social-Links');
    if (!container) return;

    const link = document.createElement('a');
    link.href = '/Main_Page';
    link.textContent = 'ICANNWiki';

    container.innerHTML = '';
    container.appendChild(link);
    container.style.opacity = 1;
    container.style.pointerEvents = 'auto';
  }

  function initSidebarIcons() {
    getSidebarIconsConfig().then(function (cfg) {
      const groups = (cfg && cfg.groups) || [];
      if (!groups.length) {
        insertTextFallback();
        return;
      }

      const allIcons = groups.flatMap(g => g.icons);
      const fileTitles = allIcons.map(icon => 'File:' + icon.file);
      const maxSize = Math.max.apply(null, groups.map(g => pickSize(g.size)));

      fetchIconURLs(fileTitles, maxSize)
        .then(urlMap => insertIconGroups(groups, urlMap))
        .catch(err => {
          console.groupCollapsed('[SidebarImages] API error occurred.');
          console.error(err);
          console.groupEnd();
          insertTextFallback();
        });
    });
  }

  $(function () {
    if (mw.config.get('skin') === 'minerva') return; // REVIEW: Skip mobile skin

    const sidebar = document.getElementById('mw-panel');
    if (!sidebar) {
      // Wait for sidebar creation on slow-loading pages
      new MutationObserver((mutations, obs) => {
        if (document.getElementById('mw-panel')) {
          obs.disconnect();
          initSidebarIcons();
        }
      }).observe(document.body, { childList: true, subtree: true });
    } else {
      initSidebarIcons();
    }
  });

})(jQuery, mediaWiki);