MediaWiki:Gadget-OralHistoryRecords-Updated.js
Appearance
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 ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[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);
}
});
})();