User:Most2dot0/common.js

From Angelina Jordan Wiki
Revision as of 11:26, 10 November 2025 by Most2dot0 (talk | contribs) (Test new dynamic Cargo loading version)

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.
//syntax highlighter
mw.loader.load('//www.mediawiki.org/w/index.php?title=MediaWiki:Gadget-DotsSyntaxHighlighter.js&action=raw&ctype=text/javascript');
syntaxHighlighterConfig = { parameterColor: "#FFDD99" }


// Auto-reload page content if marker element is present
$(document).ready(function() {
    const marker = document.getElementById('auto-reload-trigger');
    if (!marker) return;

    const period = Math.max(5, parseInt(marker.dataset.period || "30", 10)); // seconds
    const limit = parseInt(marker.dataset.limit || "10", 10); // max reloads
    let count = 0;

    // Create and insert status display
    const statusBox = $('<div id="auto-reload-status" style="margin:10px 0;padding:5px;border:1px solid #ccc;font-size:90%;width:fit-content;background:#f8f8f8;"></div>');
    statusBox.text(`Reloads done: ${count} / ${limit}`);
    $('#mw-content-text').before(statusBox);

    function reloadContent() {
        if (count >= limit) {
            statusBox.text(`Reload complete (${count}/${limit})`);
            return;
        }
        count++;
        statusBox.text(`Reloads done: ${count} / ${limit}`);

        $.ajax({
            url: location.href,
            cache: false,
            success: function(data) {
                const newContent = $(data).find('#mw-content-text');
                if (newContent.length) {
                    $('#mw-content-text').replaceWith(newContent);
                    mw.hook('wikipage.content').fire($('#mw-content-text'));
                }
            }
        });
    }

    setInterval(reloadContent, period * 1000);
});



/* Optimized CargoDynamic gadget */
window.CargoDynamic = (function () {
  'use strict';

  /* ---------- Utilities ---------- */
  const cache = new Map();
  const precompiled = new Map();

  function resolvePath(obj, path) {
    return path.split('.').reduce((o, k) => (o && o[k] !== undefined ? o[k] : null), obj);
  }

  function compileRegex(p) {
    if (!p || precompiled.has(p)) return precompiled.get(p);
    try {
      const r = new RegExp(String(p).replace(/\\\\/g, '\\').replace(/\\\//g, '/'), 'g');
      precompiled.set(p, r);
      return r;
    } catch {
      console.warn('Invalid regex:', p);
      return null;
    }
  }

  /* ---------- Formula evaluation ---------- */
  function computeField(item, formula) {
    // process {field|if|ref} ... {/endif}
    function processConditionals(text) {
      let out = '', pos = 0;
      while (true) {
        const m = text.slice(pos).match(/\{([^|}]+)\|if\|([^}]+)\}/);
        if (!m) { out += text.slice(pos); break; }
        const si = pos + m.index, sj = si + m[0].length;
        const field = m[1], ref = m[2];
        out += text.slice(pos, si);

        let depth = 1, scan = sj, elseStart = null, elseEnd = null, endPos = null;
        while (scan < text.length) {
          const t = text.slice(scan).match(/\{([^}]+)\}/);
          if (!t) break;
          const ti = scan + t.index, te = ti + t[0].length, tag = t[1];
          if (/^[^|}]+\|if\|/.test(tag)) depth++;
          else if (tag === 'else' && depth === 1) { elseStart = ti; elseEnd = te; }
          else if (tag === '/endif') {
            if (--depth === 0) {
              endPos = te;
              const thenPart = elseStart ? text.slice(sj, elseStart) : text.slice(sj, ti);
              const elsePart = elseStart ? text.slice(elseEnd, ti) : '';
              const val = resolvePath(item, field);
              const ok = ref === '' ? !!val :
                (!isNaN(+val) && !isNaN(+ref) ? +val === +ref : String(val) === ref);
              out += processConditionals(ok ? thenPart : elsePart);
              pos = te;
              break;
            }
          }
          scan = te;
        }
        if (!endPos) { out += text.slice(si); break; }
      }
      return out;
    }

    formula = processConditionals(formula);

    // simple {field|pattern|repl|toTitleCase|...}
    return formula.replace(/\{([^}]+)\}/g, (_, content) => {
      if (/^[^|}]+\|if\|/.test(content) || content === 'else' || content === '/endif') return '';
      const parts = content.split('|');
      let value = resolvePath(item, parts[0]);
      value = value != null ? String(value) : '';

      for (let i = 1; i < parts.length; i += 2) {
        const pattern = parts[i], repl = parts[i + 1];
        if (!pattern) continue;
        if (pattern === 'toUpper') value = value.toUpperCase();
        else if (pattern === 'toLower') value = value.toLowerCase();
        else if (pattern === 'toTitleCase')
          value = value.replace(/\b(\w)(\w*)/g, (_, f, r) => f.toUpperCase() + r.toLowerCase());
        else if (pattern === 'limit' && repl)
          value = value.slice(0, +repl || 0);
        else if (repl) {
          const regex = compileRegex(pattern);
          if (regex)
            value = value.replace(regex, (...args) =>
              repl.replace(/\$(\d+)/g, (_, n) => args[+n] ?? '')
            );
        }
      }
      return value;
    });
  }

  /* ---------- Core ---------- */
  function readConfig() {
    const div = document.getElementById('cargo-config');
    if (!div) return null;
    try { return JSON.parse(div.textContent); }
    catch (e) { console.error('CargoDynamic: bad JSON', e); return null; }
  }

  async function loadCargoFor($row) {
    const song = $row.data('song');
    const qname = $row.data('query');
    const rname = $row.data('render');
    const cfg = readConfig();
    const q = cfg?.queries?.[qname];
    const r = cfg?.renderers?.[rname];
    const $td = $row.find('td').first();

    if (!q || !r) { $td.html('<em>Missing query or renderer.</em>'); return; }

    const key = `${qname}|${song}`;
    if (cache.has(key)) { $td.html(cache.get(key)); return; }

    const api = new URL(mw.util.wikiScript('api'), location.origin);
    api.searchParams.set('action', 'cargoquery');
    api.searchParams.set('format', 'json');
    for (const k in q) api.searchParams.set(k, String(q[k]).replace(/\$song/g, song || ''));

    $td.html('<em>Loading…</em>');

    try {
      const data = await $.getJSON(api.toString());
      const rows = (data?.cargoquery || []).map(e => e.title);
      if (!rows.length) { $td.html('<em>No results.</em>'); return; }

      const table = document.createElement('table');
      table.className = 'wikitable sortable';
      table.style.width = '100%';
      const head = document.createElement('tr');
      r.headers.forEach(h => {
        const th = document.createElement('th');
        th.textContent = h;
        head.appendChild(th);
      });
      table.appendChild(head);

      for (const row of rows) {
        const tr = document.createElement('tr');
        for (const col of r.columns) {
          const td = document.createElement('td');
          td.innerHTML = computeField(row, col); // preserve HTML
          tr.appendChild(td);
        }
        table.appendChild(tr);
      }
      cache.set(key, table.outerHTML);
      $td.html(table);
    } catch {
      $td.html('<em>Error loading data.</em>');
    }
  }

  /* ---------- Single observer for all ---------- */
  function init() {
    const placeholders = document.querySelectorAll('.cargo-placeholder');
    if (!placeholders.length) return;

    const observer = new MutationObserver(muts => {
      for (const m of muts) {
        const el = m.target;
        if (el.matches('.cargo-placeholder') && $(el).is(':visible') && !el.dataset.loaded) {
          el.dataset.loaded = '1';
          loadCargoFor($(el));
        }
      }
    });

    placeholders.forEach(el => observer.observe(el, {
      attributes: true,
      attributeFilter: ['style', 'class']
    }));

    // also trigger load on direct click
    $(document).on('click', '.mw-customtoggle', e => {
      const id = 'mw-customcollapsible-' + e.target.className.match(/mw-customtoggle-(\S+)/)[1];
      const $row = $('#' + id);
      setTimeout(() => { if ($row.is(':visible') && !$row.data('loaded')) loadCargoFor($row); }, 100);
    });
  }

  $(init);
  return { computeField };
})();