User:Most2dot0/common.js

From Angelina Jordan Wiki
Revision as of 19:56, 6 November 2025 by Most2dot0 (talk | contribs) (rewrite, supposed to finally fix the issue that two clicks are needed)

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

/* global mw, $ */
window.CargoDynamic = (function() {
  'use strict';

  // Compute field with conditionals and transformations
  function computeField(item, formula, resolvePath, matchesFilter) {
    resolvePath = resolvePath || function(obj, path) {
      return path.split('.').reduce(function(o, k) {
        return (o && o[k] !== undefined) ? o[k] : null;
      }, obj);
    };
    matchesFilter = matchesFilter || function(value, filter) {
      if (!filter) return true;
      return String(value).indexOf(filter) >= 0;
    };

    function processConditionals(text) {
      var out = '', pos = 0;
      while (true) {
        var match = text.slice(pos).match(/\{([^:}]+):if:([^}]+)\}/);
        if (!match) { out += text.slice(pos); break; }
        var si = pos + match.index;
        var sj = si + match[0].length;
        var field = match[1];
        var ref = match[2];
        out += text.slice(pos, si);

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

    formula = processConditionals(formula);

    // Replace simple {field} placeholders
    formula = formula.replace(/\{([^}]+)\}/g, function(_, content) {
      if (/^[^:}]+:if:/.test(content) || content === 'else' || content === '/endif') return '';
      var parts = content.split(':');
      var fieldPath = parts[0];
      var value = resolvePath(item, fieldPath);
      value = (value !== null && value !== undefined) ? String(value) : '';

      for (var i = 1; i < parts.length; i += 2) {
        var pattern = parts[i], repl = parts[i + 1];
        if (pattern === 'toUpper') value = value.toUpperCase();
        else if (pattern === 'toLower') value = value.toLowerCase();
        else if (pattern === 'toTitleCase') value = value.replace(/\b(\w)(\w*)/g, function(_, f, r) { return f.toUpperCase() + r; });
        else if (pattern === 'limit' && repl) {
          var n = Number(repl);
          if (!isNaN(n)) value = value.slice(0, n);
        } else if (repl) {
          value = value.replace(new RegExp(pattern, 'g'), repl);
        }
      }
      return value;
    });

    return formula;
  }

  function readConfig() {
    var div = document.getElementById('cargo-config');
    if (!div) return null;
    try { return JSON.parse(div.textContent); } 
    catch (e) { console.error('CargoDynamic: invalid JSON in #cargo-config', e); return null; }
  }

  function loadCargoFor($row) {
    let $content = $row.find('.mw-collapsible-content');
    if (!$content.length) $content = $row.find('td').first();

    const song = $row.data('song');
    const queryName = $row.data('query');
    const renderName = $row.data('render');

    const config = readConfig();
    const query = config?.queries?.[queryName] || null;
    const renderer = config?.renderers?.[renderName] || null;

    if (!query || !renderer) {
      $content.html('<em style="color:red;">Error: missing query or renderer definition</em>');
      return;
    }

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

    const apiUrl = new URL(mw.util.wikiScript('api'), location.origin);
    apiUrl.searchParams.set('action', 'cargoquery');
    apiUrl.searchParams.set('format', 'json');
    Object.keys(query).forEach(k => apiUrl.searchParams.set(k, String(query[k]).replace(/\$song/g, song || '')));

    $.getJSON(apiUrl.toString())
      .done(data => {
        const results = data?.cargoquery?.map(e => e.title) || [];
        if (!results.length) {
          $content.html('<em>No results found.</em>');
          return;
        }

        const rows = results.map(r => 
          '<tr>' + renderer.columns.map(c => '<td>' + computeField(r, c) + '</td>').join('') + '</tr>'
        );

        const table = '<table class="wikitable sortable" style="width:100%;">' +
                      '<tr>' + renderer.headers.map(h => '<th>' + h + '</th>').join('') + '</tr>' +
                      rows.join('') + '</table>';

        $content.html(table);
      })
      .fail(() => {
        $content.html('<em style="color:red;">Error loading data.</em>');
      });
  }

  function init() {
    $('.cargo-placeholder').each(function() {
      const $row = $(this);

      function triggerLoad() {
        if ($row.data('loaded')) return;
        $row.data('loaded', true);
        loadCargoFor($row);
      }

      // MutationObserver as backup (row becomes visible via MW)
      const observer = new MutationObserver(() => { triggerLoad(); });
      observer.observe($row[0], { attributes: true, attributeFilter: ['class','style'] });

      // Hook on the toggle span above
      const toggle = $row.prev().find('.mw-customtoggle');
      if (toggle.length) {
        toggle.on('click', () => {
          // call after MW toggle
          setTimeout(triggerLoad, 0);
        });
      }

      // Initial check
      triggerLoad();
    });
  }

  $(init);

  return { computeField, readConfig };
})();