Jump to content

MediaWiki:Common.js: Difference between revisions

From ICE List Wiki
Open volunteer content/triage to all logged-in users (was group-restricted)
Rewrite client JS as dependency-safe vanilla (bare $ was throwing on content pages, killing vol-member/embeds/marks)
Line 122: Line 122:
}
}


/* Homepage: strip native title tooltips on interactive tiles (they duplicate/contradict the visible labels) */
/* ============ ICE List client tools (vanilla; no bare $ dependency) ============ */
$(function () {
(function () {
   if (mw.config.get('wgPageName') === 'Main_Page') {
   function ready(fn) { if (document.readyState !== 'loading') { fn(); } else { document.addEventListener('DOMContentLoaded', fn); } }
    $('.ic-scenarios a, .ic-stat a, .ic-hero__actions a').removeAttr('title');
  }
});
 


/* ===================== Volunteer tools + media embeds ===================== */
  /* Reveal volunteer-only content for ANY logged-in user */
/* Mark logged-in volunteers so .vol-only content can show */
   try { if (window.mw && mw.user && !mw.user.isAnon()) { document.documentElement.classList.add('vol-member'); } } catch (e) {}
(function () {
  /* Volunteer content is open to ALL logged-in users */
   if (!mw.user.isAnon()) { document.documentElement.classList.add('vol-member'); }
})();


/* Render video / social embeds (all users, client-side for privacy) */
  /* Homepage: strip native title tooltips on interactive tiles */
$(function () {
   ready(function () {
  var nodes = document.querySelectorAll('.ic-embed:not([data-done])');
     if (mw.config.get('wgPageName') === 'Main_Page') {
  if (!nodes.length) { return; }
       document.querySelectorAll('.ic-scenarios a, .ic-stat a, .ic-hero__actions a').forEach(function (a) { a.removeAttribute('title'); });
  var needX = false, needIG = false, needTh = false;
   Array.prototype.forEach.call(nodes, function (el) {
    el.setAttribute('data-done', '1');
    var id = el.getAttribute('data-id'), url = el.getAttribute('data-url');
     if (el.classList.contains('ic-embed-youtube') && id) {
      el.innerHTML = '<iframe loading="lazy" allowfullscreen allow="encrypted-media; picture-in-picture" src="https://www.youtube-nocookie.com/embed/' + encodeURIComponent(id) + '"></iframe>';
    } else if (el.classList.contains('ic-embed-vimeo') && id) {
       el.innerHTML = '<iframe loading="lazy" allowfullscreen src="https://player.vimeo.com/video/' + encodeURIComponent(id) + '"></iframe>';
    } else if (el.classList.contains('ic-embed-x') && url) {
      el.innerHTML = '<blockquote class="twitter-tweet"><a href="' + url + '"></a></blockquote>'; needX = true;
    } else if (el.classList.contains('ic-embed-instagram') && url) {
      el.innerHTML = '<blockquote class="instagram-media" data-instgrm-permalink="' + url + '" data-instgrm-version="14"></blockquote>'; needIG = true;
    } else if (el.classList.contains('ic-embed-threads') && url) {
      el.innerHTML = '<blockquote class="text-post-media" data-text-post-permalink="' + url + '"></blockquote>'; needTh = true;
     }
     }
   });
   });
  function load(src) { var sc = document.createElement('script'); sc.async = true; sc.src = src; document.body.appendChild(sc); }
  if (needX) { load('https://platform.twitter.com/widgets.js'); }
  if (needIG) { load('https://www.instagram.com/embed.js'); }
  if (needTh) { load('https://www.threads.net/embed.js'); }
});


/* Volunteer incident marking tool: checkbox panel that saves triage marks via the API */
  /* Render video / social embeds (all users, client-side for privacy) */
$(function () {
  ready(function () {
  if (!document.documentElement.classList.contains('vol-member')) { return; }
    var nodes = document.querySelectorAll('.ic-embed:not([data-done])');
  if (mw.config.get('wgAction') !== 'view') { return; }
    if (!nodes.length) { return; }
  var cats = mw.config.get('wgCategories') || [];
    var needX = false, needIG = false, needTh = false;
  if (cats.indexOf('Incidents') === -1) { return; }
    nodes.forEach(function (el) {
  var content = document.querySelector('#mw-content-text .mw-parser-output');
      el.setAttribute('data-done', '1');
  if (!content) { return; }
      var id = el.getAttribute('data-id'), url = el.getAttribute('data-url');
  var MARKS = ['Agents recorded', 'Plates recorded', 'Faces captured', 'Video secured', 'Location confirmed', 'Witnesses identified', 'Needs review', 'Reviewed'];
      if (el.classList.contains('ic-embed-youtube') && id) {
  mw.loader.using(['mediawiki.api']).then(function () {
        el.innerHTML = '<iframe loading="lazy" allowfullscreen allow="encrypted-media; picture-in-picture" src="https://www.youtube-nocookie.com/embed/' + encodeURIComponent(id) + '"></iframe>';
    var api = new mw.Api();
      } else if (el.classList.contains('ic-embed-vimeo') && id) {
    var page = mw.config.get('wgPageName');
        el.innerHTML = '<iframe loading="lazy" allowfullscreen src="https://player.vimeo.com/video/' + encodeURIComponent(id) + '"></iframe>';
    var panel = document.createElement('div');
      } else if (el.classList.contains('ic-embed-x') && url) {
    panel.className = 'vol-panel';
        el.innerHTML = '<blockquote class="twitter-tweet"><a href="' + url + '"></a></blockquote>'; needX = true;
    panel.innerHTML = '<h4>Volunteer triage <span class="vol-status">loading…</span></h4>';
      } else if (el.classList.contains('ic-embed-instagram') && url) {
    var status = panel.querySelector('.vol-status');
        el.innerHTML = '<blockquote class="instagram-media" data-instgrm-permalink="' + url + '" data-instgrm-version="14"></blockquote>'; needIG = true;
    var boxes = {};
       } else if (el.classList.contains('ic-embed-threads') && url) {
    MARKS.forEach(function (m) {
        el.innerHTML = '<blockquote class="text-post-media" data-text-post-permalink="' + url + '"></blockquote>'; needTh = true;
      var lab = document.createElement('label');
      }
      var cb = document.createElement('input'); cb.type = 'checkbox'; cb.disabled = true;
       lab.appendChild(cb); lab.appendChild(document.createTextNode(' ' + m));
      panel.appendChild(lab); boxes[m] = cb;
     });
     });
     content.insertBefore(panel, content.firstChild);
     function load(src) { var sc = document.createElement('script'); sc.async = true; sc.src = src; document.body.appendChild(sc); }
    var wikitext = '';
     if (needX) { load('https://platform.twitter.com/widgets.js'); }
     function parseMarks(wt) {
    if (needIG) { load('https://www.instagram.com/embed.js'); }
      var re = /\{\{\s*Mark\s*\|\s*([^}|]+?)\s*\}\}/g, mm, set = {};
     if (needTh) { load('https://www.threads.net/embed.js'); }
      while ((mm = re.exec(wt))) { set[mm[1].trim()] = true; }
  });
      return set;
 
     }
  /* Volunteer incident marking tool: checkbox panel that saves triage marks via the API */
    api.get({ action: 'query', prop: 'revisions', rvprop: 'content', rvslots: 'main', titles: page, formatversion: 2 }).then(function (r) {
  ready(function () {
      try { wikitext = r.query.pages[0].revisions[0].slots.main.content; } catch (e) { wikitext = ''; }
    if (!document.documentElement.classList.contains('vol-member')) { return; }
      var set = parseMarks(wikitext);
    if (mw.config.get('wgAction') !== 'view') { return; }
      MARKS.forEach(function (m) { boxes[m].checked = !!set[m]; boxes[m].disabled = false; });
    var cats = mw.config.get('wgCategories') || [];
      status.textContent = '';
    if (cats.indexOf('Incidents') === -1) { return; }
    });
    var content = document.querySelector('#mw-content-text .mw-parser-output');
     function save() {
     if (!content) { return; }
      status.textContent = 'saving…';
    var MARKS = ['Agents recorded', 'Plates recorded', 'Faces captured', 'Video secured', 'Location confirmed', 'Witnesses identified', 'Needs review', 'Reviewed'];
      MARKS.forEach(function (m) { boxes[m].disabled = true; });
    mw.loader.using(['mediawiki.api']).then(function () {
       var chosen = MARKS.filter(function (m) { return boxes[m].checked; });
      var api = new mw.Api();
       var inner = chosen.map(function (m) { return '{{Mark|' + m + '}}'; }).join('');
       var page = mw.config.get('wgPageName');
       var region = '<div class="vol-only vol-marks"><!--VOLMARKS-->' + inner + '<!--/VOLMARKS--></div>';
       var panel = document.createElement('div');
       var done = false, wt = wikitext;
      panel.className = 'vol-panel';
       wt = wikitext.replace(/<div class="vol-only vol-marks"><!--VOLMARKS-->[\s\S]*?<!--\/VOLMARKS--><\/div>/, function () { done = true; return region; });
       panel.innerHTML = '<h4>Volunteer triage <span class="vol-status">loading…</span></h4>';
      if (!done) { wt = wikitext.replace(/<!--VOLMARKS-->[\s\S]*?<!--\/VOLMARKS-->/, function () { done = true; return '<!--VOLMARKS-->' + inner + '<!--/VOLMARKS-->'; }); }
       var status = panel.querySelector('.vol-status');
       if (!done) {
      var boxes = {};
         var idx = wt.search(/\n\[\[Category:/);
       MARKS.forEach(function (m) {
         if (idx >= 0) { wt = wt.slice(0, idx) + '\n' + region + wt.slice(idx); }
        var lab = document.createElement('label');
         else { wt = wt.replace(/\s*$/, '') + '\n\n' + region + '\n'; }
        var cb = document.createElement('input'); cb.type = 'checkbox'; cb.disabled = true;
        lab.appendChild(cb); lab.appendChild(document.createTextNode(' ' + m));
        panel.appendChild(lab); boxes[m] = cb;
      });
      content.insertBefore(panel, content.firstChild);
      var wikitext = '';
       function parseMarks(wt) {
         var re = /\{\{\s*Mark\s*\|\s*([^}|]+?)\s*\}\}/g, mm, set = {};
         while ((mm = re.exec(wt))) { set[mm[1].trim()] = true; }
         return set;
       }
       }
       api.postWithToken('csrf', { action: 'edit', title: page, text: wt, summary: 'Update volunteer triage marks' }).then(function () {
       api.get({ action: 'query', prop: 'revisions', rvprop: 'content', rvslots: 'main', titles: page, formatversion: 2 }).then(function (r) {
         wikitext = wt; status.textContent = 'saved ✓';
         try { wikitext = r.query.pages[0].revisions[0].slots.main.content; } catch (e) { wikitext = ''; }
        MARKS.forEach(function (m) { boxes[m].disabled = false; });
        var set = parseMarks(wikitext);
        setTimeout(function () { status.textContent = ''; }, 1500);
         MARKS.forEach(function (m) { boxes[m].checked = !!set[m]; boxes[m].disabled = false; });
      }).catch(function () {
        status.textContent = '';
         status.textContent = 'save failed'; MARKS.forEach(function (m) { boxes[m].disabled = false; });
       });
       });
    }
      function save() {
    MARKS.forEach(function (m) { boxes[m].addEventListener('change', save); });
        status.textContent = 'saving…';
        MARKS.forEach(function (m) { boxes[m].disabled = true; });
        var chosen = MARKS.filter(function (m) { return boxes[m].checked; });
        var inner = chosen.map(function (m) { return '{{Mark|' + m + '}}'; }).join('');
        var region = '<div class="vol-only vol-marks"><!--VOLMARKS-->' + inner + '<!--/VOLMARKS--></div>';
        var done = false, wt = wikitext;
        wt = wikitext.replace(/<div class="vol-only vol-marks"><!--VOLMARKS-->[\s\S]*?<!--\/VOLMARKS--><\/div>/, function () { done = true; return region; });
        if (!done) { wt = wikitext.replace(/<!--VOLMARKS-->[\s\S]*?<!--\/VOLMARKS-->/, function () { done = true; return '<!--VOLMARKS-->' + inner + '<!--/VOLMARKS-->'; }); }
        if (!done) {
          var idx = wt.search(/\n\[\[Category:/);
          if (idx >= 0) { wt = wt.slice(0, idx) + '\n' + region + wt.slice(idx); }
          else { wt = wt.replace(/\s*$/, '') + '\n\n' + region + '\n'; }
        }
        api.postWithToken('csrf', { action: 'edit', title: page, text: wt, summary: 'Update volunteer triage marks' }).then(function () {
          wikitext = wt; status.textContent = 'saved ✓';
          MARKS.forEach(function (m) { boxes[m].disabled = false; });
          setTimeout(function () { status.textContent = ''; }, 1500);
        }).catch(function () {
          status.textContent = 'save failed'; MARKS.forEach(function (m) { boxes[m].disabled = false; });
        });
      }
      MARKS.forEach(function (m) { boxes[m].addEventListener('change', save); });
    });
   });
   });
});
})();
/* ===================== end volunteer tools + media embeds ===================== */
/* ============ end ICE List client tools ============ */

Revision as of 05:29, 9 July 2026

/* Load CRUST brand fonts — ResourceLoader strips @import in Common.css, so inject the link here */
(function(){
  var l=document.createElement('link'); l.rel='stylesheet';
  l.href='https://fonts.googleapis.com/css2?family=Anton&family=Libre+Franklin:wght@400;500;600;700;800;900&family=Spline+Sans+Mono:wght@400;500;600&display=swap';
  document.head.appendChild(l);
})();

(function () {
  'use strict';

  var KEY = 'icelist_anon_donate_dismiss_until';
  var DAYS = 14;

  if (!mw.user.isAnon()) return;

  var until = parseInt(localStorage.getItem(KEY) || '0', 10);
  if (Date.now() < until) return;

  function mount() {
    var siteNotice = document.getElementById('siteNotice');
    if (!siteNotice) return;

    // If empty, do nothing
    var txt = (siteNotice.textContent || '').replace(/\s+/g, ' ').trim();
    if (!txt) return;

    siteNotice.classList.add('icelist-anon-donate');

    // Add close button once
    if (!siteNotice.querySelector('.icelist-notice-close')) {
      var btn = document.createElement('button');
      btn.type = 'button';
      btn.className = 'icelist-notice-close';
      btn.setAttribute('aria-label', 'Close');
      btn.textContent = '×';
      siteNotice.insertBefore(btn, siteNotice.firstChild);

      btn.addEventListener('click', function (e) {
        e.preventDefault();
        siteNotice.style.display = 'none';
        localStorage.setItem(KEY, String(Date.now() + DAYS * 24 * 60 * 60 * 1000));
      });
    }
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', mount);
  } else {
    mount();
  }
})();

mw.loader.using(['jquery']).then(function () {
    var s = document.createElement('script');
    s.src = 'https://cdnjs.buymeacoffee.com/1.0.0/widget.prod.min.js';
    s.setAttribute('data-name', 'BMC-Widget');
    s.setAttribute('data-cfasync', 'false');
    s.setAttribute('data-id', 'crustnews');
    s.setAttribute('data-description', 'Support me on Buy me a coffee!');
    s.setAttribute('data-message', '');
    s.setAttribute('data-color', '#FF5F5F');
    s.setAttribute('data-position', 'Right');
    s.setAttribute('data-x_margin', '18');
    s.setAttribute('data-y_margin', '18');
    document.body.appendChild(s);
});
// Tag agent pages (any page showing an agent card) so the duplicate title/heading can be hidden
if ( document.querySelector( '.ic-agent-card' ) ) {
    document.body.classList.add( 'ic-agent-page' );
}

/* ---- Mark anonymous users so anon-only banners can show (fail-safe: hidden by default) ---- */
if (mw.user.isAnon()) { document.documentElement.classList.add('vol-anon'); }

/* ---- Incident candidates page: logged-in-only + per-row dismiss ---- */
if (/Incident_candidates$/.test(mw.config.get('wgPageName'))) {
  mw.loader.using(['mediawiki.api', 'mediawiki.util']).then(function () {
    var content = document.querySelector('#mw-content-text .mw-parser-output');
    if (!content) return;
    if (mw.user.isAnon()) {
      content.innerHTML = '<div class="vol-login">This page is for logged-in ICE List volunteers. ' +
        '<a href="' + mw.util.getUrl('Special:UserLogin', {returnto: mw.config.get('wgPageName')}) +
        '">Log in</a> to review incident candidates.</div>';
      return;
    }
    var api = new mw.Api();
    content.querySelectorAll('li').forEach(function (li) {
      var create = Array.prototype.find.call(li.querySelectorAll('a'), function (a) {
        return /FormEdit\/Incident/.test(a.getAttribute('href') || '');
      });
      if (!create) return;
      var art = Array.prototype.find.call(li.querySelectorAll('a[href^="http"]'), function (a) {
        return a !== create && !/FormEdit\/Incident/.test(a.getAttribute('href') || '');
      });
      var url = art ? art.href : null;
      var btn = document.createElement('button');
      btn.textContent = '✕ dismiss';
      btn.className = 'vol-dismiss';
      btn.onclick = function () {
        if (!url) { li.remove(); return; }
        btn.disabled = true; btn.textContent = '…';
        api.get({action: 'parse', page: mw.config.get('wgPageName'), prop: 'wikitext', formatversion: 2})
          .then(function (r) {
            var lines = r.parse.wikitext.split('\n'), out = [], removed = false;
            for (var i = 0; i < lines.length; i++) {
              if (!removed && lines[i].indexOf(url) !== -1) {
                if (out.length && /^<!-- cand:/.test(out[out.length - 1])) out.pop();
                removed = true; continue;
              }
              out.push(lines[i]);
            }
            return api.postWithToken('csrf', {action: 'edit', title: mw.config.get('wgPageName'),
              text: out.join('\n'), summary: 'Dismiss incident candidate'});
          })
          .then(function () { li.remove(); })
          .catch(function () { btn.disabled = false; btn.textContent = '✕ dismiss'; });
      };
      li.appendChild(document.createTextNode(' '));
      li.appendChild(btn);
    });
  });
}

/* ============ ICE List client tools (vanilla; no bare $ dependency) ============ */
(function () {
  function ready(fn) { if (document.readyState !== 'loading') { fn(); } else { document.addEventListener('DOMContentLoaded', fn); } }

  /* Reveal volunteer-only content for ANY logged-in user */
  try { if (window.mw && mw.user && !mw.user.isAnon()) { document.documentElement.classList.add('vol-member'); } } catch (e) {}

  /* Homepage: strip native title tooltips on interactive tiles */
  ready(function () {
    if (mw.config.get('wgPageName') === 'Main_Page') {
      document.querySelectorAll('.ic-scenarios a, .ic-stat a, .ic-hero__actions a').forEach(function (a) { a.removeAttribute('title'); });
    }
  });

  /* Render video / social embeds (all users, client-side for privacy) */
  ready(function () {
    var nodes = document.querySelectorAll('.ic-embed:not([data-done])');
    if (!nodes.length) { return; }
    var needX = false, needIG = false, needTh = false;
    nodes.forEach(function (el) {
      el.setAttribute('data-done', '1');
      var id = el.getAttribute('data-id'), url = el.getAttribute('data-url');
      if (el.classList.contains('ic-embed-youtube') && id) {
        el.innerHTML = '<iframe loading="lazy" allowfullscreen allow="encrypted-media; picture-in-picture" src="https://www.youtube-nocookie.com/embed/' + encodeURIComponent(id) + '"></iframe>';
      } else if (el.classList.contains('ic-embed-vimeo') && id) {
        el.innerHTML = '<iframe loading="lazy" allowfullscreen src="https://player.vimeo.com/video/' + encodeURIComponent(id) + '"></iframe>';
      } else if (el.classList.contains('ic-embed-x') && url) {
        el.innerHTML = '<blockquote class="twitter-tweet"><a href="' + url + '"></a></blockquote>'; needX = true;
      } else if (el.classList.contains('ic-embed-instagram') && url) {
        el.innerHTML = '<blockquote class="instagram-media" data-instgrm-permalink="' + url + '" data-instgrm-version="14"></blockquote>'; needIG = true;
      } else if (el.classList.contains('ic-embed-threads') && url) {
        el.innerHTML = '<blockquote class="text-post-media" data-text-post-permalink="' + url + '"></blockquote>'; needTh = true;
      }
    });
    function load(src) { var sc = document.createElement('script'); sc.async = true; sc.src = src; document.body.appendChild(sc); }
    if (needX) { load('https://platform.twitter.com/widgets.js'); }
    if (needIG) { load('https://www.instagram.com/embed.js'); }
    if (needTh) { load('https://www.threads.net/embed.js'); }
  });

  /* Volunteer incident marking tool: checkbox panel that saves triage marks via the API */
  ready(function () {
    if (!document.documentElement.classList.contains('vol-member')) { return; }
    if (mw.config.get('wgAction') !== 'view') { return; }
    var cats = mw.config.get('wgCategories') || [];
    if (cats.indexOf('Incidents') === -1) { return; }
    var content = document.querySelector('#mw-content-text .mw-parser-output');
    if (!content) { return; }
    var MARKS = ['Agents recorded', 'Plates recorded', 'Faces captured', 'Video secured', 'Location confirmed', 'Witnesses identified', 'Needs review', 'Reviewed'];
    mw.loader.using(['mediawiki.api']).then(function () {
      var api = new mw.Api();
      var page = mw.config.get('wgPageName');
      var panel = document.createElement('div');
      panel.className = 'vol-panel';
      panel.innerHTML = '<h4>Volunteer triage <span class="vol-status">loading…</span></h4>';
      var status = panel.querySelector('.vol-status');
      var boxes = {};
      MARKS.forEach(function (m) {
        var lab = document.createElement('label');
        var cb = document.createElement('input'); cb.type = 'checkbox'; cb.disabled = true;
        lab.appendChild(cb); lab.appendChild(document.createTextNode(' ' + m));
        panel.appendChild(lab); boxes[m] = cb;
      });
      content.insertBefore(panel, content.firstChild);
      var wikitext = '';
      function parseMarks(wt) {
        var re = /\{\{\s*Mark\s*\|\s*([^}|]+?)\s*\}\}/g, mm, set = {};
        while ((mm = re.exec(wt))) { set[mm[1].trim()] = true; }
        return set;
      }
      api.get({ action: 'query', prop: 'revisions', rvprop: 'content', rvslots: 'main', titles: page, formatversion: 2 }).then(function (r) {
        try { wikitext = r.query.pages[0].revisions[0].slots.main.content; } catch (e) { wikitext = ''; }
        var set = parseMarks(wikitext);
        MARKS.forEach(function (m) { boxes[m].checked = !!set[m]; boxes[m].disabled = false; });
        status.textContent = '';
      });
      function save() {
        status.textContent = 'saving…';
        MARKS.forEach(function (m) { boxes[m].disabled = true; });
        var chosen = MARKS.filter(function (m) { return boxes[m].checked; });
        var inner = chosen.map(function (m) { return '{{Mark|' + m + '}}'; }).join('');
        var region = '<div class="vol-only vol-marks"><!--VOLMARKS-->' + inner + '<!--/VOLMARKS--></div>';
        var done = false, wt = wikitext;
        wt = wikitext.replace(/<div class="vol-only vol-marks"><!--VOLMARKS-->[\s\S]*?<!--\/VOLMARKS--><\/div>/, function () { done = true; return region; });
        if (!done) { wt = wikitext.replace(/<!--VOLMARKS-->[\s\S]*?<!--\/VOLMARKS-->/, function () { done = true; return '<!--VOLMARKS-->' + inner + '<!--/VOLMARKS-->'; }); }
        if (!done) {
          var idx = wt.search(/\n\[\[Category:/);
          if (idx >= 0) { wt = wt.slice(0, idx) + '\n' + region + wt.slice(idx); }
          else { wt = wt.replace(/\s*$/, '') + '\n\n' + region + '\n'; }
        }
        api.postWithToken('csrf', { action: 'edit', title: page, text: wt, summary: 'Update volunteer triage marks' }).then(function () {
          wikitext = wt; status.textContent = 'saved ✓';
          MARKS.forEach(function (m) { boxes[m].disabled = false; });
          setTimeout(function () { status.textContent = ''; }, 1500);
        }).catch(function () {
          status.textContent = 'save failed'; MARKS.forEach(function (m) { boxes[m].disabled = false; });
        });
      }
      MARKS.forEach(function (m) { boxes[m].addEventListener('change', save); });
    });
  });
})();
/* ============ end ICE List client tools ============ */