Jump to content

MediaWiki:Gadget-OralHistoryRecords-Updated.js

Revision as of 17:43, 22 February 2026 by Hthach (talk | contribs)

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.
/* ===== OralHistoryRecords-Updated.js (Grouped by Person Key, Option 2) ===== */
/* global mw */
(function () {
  'use strict';

  // ====== COLUMN CONFIG (MUST MATCH CSV HEADERS EXACTLY) ======
  var COL = {
    personKey: 'Person Key',
    first: 'First Name',
    last: 'Last Name',
    birth: 'Birth Year',
    death: 'Death Year',
    dateFrom: 'Date From',
    dateTo: 'Date To',
    bio: 'Short Bio',
    wiki: 'Wiki Link',
    audio: 'Audio Link',
    video: 'Video Link',
    transcripts: 'Transcript Link'
  };

  var PEOPLE = [];
  var FILTER = { q: '' };

  function $(id) { return document.getElementById(id); }

  function onReady(fn) {
    if (document.readyState !== 'loading') fn();
    else document.addEventListener('DOMContentLoaded', fn);
  }

  function esc(s) {
    s = (s == null ? '' : String(s));
    return s.replace(/[&<>"']/g, function (c) {
      return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]);
    });
  }

  function norm(s) {
    return (s || '')
      .toString()
      .normalize('NFD')
      .replace(/[\u0300-\u036f]/g, '')
      .toLowerCase();
  }

  function debounce(fn, ms) {
    var t;
    return function () {
      var a = arguments, self = this;
      clearTimeout(t);
      t = setTimeout(function () { fn.apply(self, a); }, ms);
    };
  }

  function getCsvUrl() {
    var root = $('ohr-directory');
    var override = root ? (root.getAttribute('data-csv') || '').trim() : '';
    var base = override || ((mw.config.get('wgScript') || '/index.php') + '/Special:FilePath/OHData.csv');
    return base + (base.indexOf('?') === -1 ? '?' : '&') + 'cb=' + Date.now();
  }

  function toYear(value) {
    if (!value) return '';
    var s = String(value).trim();
    var m = s.match(/\b(1[6-9]\d{2}|20\d{2}|21\d{2})\b/);
    return m ? m[1] : '';
  }

  function recordedLabel(row) {
    var y1 = toYear(row[COL.dateFrom]);
    var y2 = toYear(row[COL.dateTo]);
    if (y1 && y2) return (y1 === y2) ? ('Recorded ' + y1) : ('Recorded ' + y1 + '–' + y2);
    if (y1) return 'Recorded ' + y1;
    if (y2) return 'Recorded ' + y2;
    return 'Recorded (date unknown)';
  }

  function fullName(row) {
    var first = (row[COL.first] || '').trim();
    var last = (row[COL.last] || '').trim();
    if (!first || !last) return '';
    return (first + ' ' + last).trim();
  }

  function lifespan(row) {
    var b = (row[COL.birth] || '').trim();
    var d = (row[COL.death] || '').trim();
    if (b && d) return b + '–' + d;
    if (b && !d) return b + '–';
    if (!b && d) return '–' + d;
    return '';
  }

  function extractUrls(value) {
    if (!value) return [];
    var s = String(value);
    var re = /https?:\/\/[^\s"'<>()]+/g;
    var m = s.match(re) || [];
    var seen = Object.create(null);
    var out = [];
    for (var i = 0; i < m.length; i++) {
      var url = m[i].trim().replace(/[);.,]+$/g, '');
      if (!url) continue;
      if (!seen[url]) {
        seen[url] = true;
        out.push(url);
      }
    }
    return out;
  }

  function buildToolbar(root) {
    var toolbar = document.createElement('div');
    toolbar.className = 'ohr-toolbar';

    var search = document.createElement('input');
    search.id = 'ohr-search';
    search.type = 'search';
    search.placeholder = 'Search names, bios…';
    search.setAttribute('aria-label', 'Search oral history records');

    var count = document.createElement('div');
    count.id = 'ohr-count';
    count.className = 'ohr-count';
    count.setAttribute('aria-live', 'polite');

    toolbar.appendChild(search);
    toolbar.appendChild(count);
    root.insertBefore(toolbar, root.firstChild);
  }

  function makeAccordionPerson(person, idx) {
    var div = document.createElement('div');
    div.className = 'ohr-item';

    var panelId = 'ohr-panel-' + idx;
    var btnId = 'ohr-btn-' + idx;

    div.innerHTML =
      '<div class="ohr-head">' +
        '<div class="ohr-titleblock">' +
          '<h3 class="ohr-name">' + esc(person.first + ' ' + person.last) + '</h3>' +
        '</div>' +
        '<button class="ohr-acc-btn" id="' + btnId + '" type="button" aria-expanded="false" aria-controls="' + panelId + '">Details</button>' +
      '</div>' +
      '<div class="ohr-acc-panel ohr-hidden" id="' + panelId + '">' +
        person.content +
      '</div>';

    var btn = div.querySelector('#' + btnId);
    var panel = div.querySelector('#' + panelId);

    if (btn && panel) {
      btn.addEventListener('click', function () {
        var isOpen = !panel.classList.contains('ohr-hidden');

        if (isOpen) {
          panel.classList.add('ohr-hidden');
          div.classList.remove('is-open');
          btn.setAttribute('aria-expanded', 'false');
          btn.textContent = 'Details';
        } else {
          panel.classList.remove('ohr-hidden');
          div.classList.add('is-open');
          btn.setAttribute('aria-expanded', 'true');
          btn.textContent = 'Hide';
        }
      });
    }

    return div;
  }

  function fetchAndRender() {
    var root = $('ohr-directory');
    if (!root) return;

    buildToolbar(root);
  }

  onReady(function () {
    if ($('ohr-directory')) {
      mw.loader.using([]).then(fetchAndRender);
    }
  });

})();