User:Most2dot0/common.js

From Angelina Jordan Wiki
Revision as of 20:11, 7 November 2025 by Most2dot0 (talk | contribs) (bugfix)

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


/* test AJAX load of cargo query templates */
// Reusable function to load one Cargo table
/* global mw, $ */
window.CargoDynamic = (function() {
  'use strict';

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

    // Process conditionals {field:if:ref} ... {/endif}
    function processConditionals(text) {
        let out = '', pos = 0;
        while (pos < text.length) {
            const match = text.slice(pos).match(/\{([^|}]+)\|if\|([^}]+)\}/);
            if (!match) { out += text.slice(pos); break; }

            const si = pos + match.index;
            const sj = si + match[0].length;
            const field = match[1];
            const ref = match[2];

            out += text.slice(pos, si);

            // find matching {/endif}, accounting for nested ifs
            let depth = 1, scan = sj, elseStart = null, elseEnd = null, endPos = null;
            while (scan < text.length) {
                const tagMatch = text.slice(scan).match(/\{([^}]+)\}/);
                if (!tagMatch) break;
                const ti = scan + tagMatch.index;
                const te = ti + tagMatch[0].length;
                const 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;
                        const thenPart = elseStart ? text.slice(sj, elseStart) : text.slice(sj, ti);
                        const elsePart = elseStart ? text.slice(elseEnd, ti) : '';

                        const val = resolvePath(item, field);
                        let ok;
                        if (ref === '') ok = val !== null && val !== undefined && val !== '';
                        else {
                            const v = (val !== null && val !== undefined) ? String(val) : '';
                            const 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 placeholders {field|trans1|trans2...}
    formula = formula.replace(/\{([^{}]+)\}/g, function(_, content) {
        if (/^[^:}]+:if:/.test(content) || content === 'else' || content === '/endif') return '';
        const parts = content.split('|');
        const fieldPath = parts[0].trim();
        let value = resolvePath(item, fieldPath);
        value = (value !== null && value !== undefined) ? String(value) : '';

        for (let i = 1; i < parts.length; i++) {
            const cmd = parts[i].trim();
            if (cmd === 'toUpper') value = value.toUpperCase();
            else if (cmd === 'toLower') value = value.toLowerCase();
            else if (cmd === 'toTitleCase') value = value.replace(/\b(\w)(\w*)/g, (_, f, r) => f.toUpperCase() + r);
            else if (cmd.startsWith('limit:')) {
                const n = Number(cmd.split(':')[1]);
                if (!isNaN(n)) value = value.slice(0, n);
            }
            else if (cmd.startsWith('regex:')) {
                // syntax: regex:pattern:replacement
                const m = cmd.match(/^regex:(.+?):(.+)$/);
                if (m) {
                    const pattern = new RegExp(m[1], 'g');
                    const repl = m[2].replace(/\$(\d+)/g, (_, n) => '$' + n); // keep $1,$2 intact
                    value = value.replace(pattern, 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) {
  var $content = $row.find('.mw-collapsible-content, .cargo-content');
  if (!$content.length) {
    // fallback: create container if missing
    $content = $('<div class="cargo-content"></div>').appendTo($row.find('td').first());
  }

  var song = $row.data('song');
  var queryName = $row.data('query');
  var renderName = $row.data('render');
  var config = readConfig();
  var query = (config && config.queries && config.queries[queryName]) || null;
  var renderer = (config && config.renderers && config.renderers[renderName]) || null;

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

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

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

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

    var rows = results.map(function(r) {
      return '<tr>' + renderer.columns.map(function(c) {
        return '<td>' + computeField(r, c) + '</td>';
      }).join('') + '</tr>';
    });

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

    $content.html(table);
  }).fail(function() {
    $content.html('<em>Error loading data.</em>');
  });
}

function init() {
  $('.cargo-placeholder').each(function() {
    const $row = $(this);
    let $content = $row.find('.mw-collapsible-content, .cargo-content');
    if (!$content.length) {
      // fallback: create container if missing
      $content = $('<div class="cargo-content"></div>').appendTo($row.find('td').first());
    }

    function triggerLoad() {
      if ($row.data('loaded')) return;
      $row.data('loaded', true);
      $content.html('<em>Loading…</em>');
      loadCargoFor($row);
    }

    // Observe visibility changes
    const observer = new MutationObserver(() => {
      if ($row.is(':visible')) triggerLoad();
    });
    observer.observe(this, {
      attributes: true,
      attributeFilter: ['class','style'],
      subtree: true
    });

    // Optional fallback for click toggle
    $row.on('click', () => {
      setTimeout(() => {
        if ($row.is(':visible')) triggerLoad();
      }, 100);
    });

    // Initial check
    if ($row.is(':visible')) triggerLoad();
  });
}

  $(init);

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