Jump to content

MediaWiki:Gadget-SidebarImagesMobile.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.
/**
 * 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);