MediaWiki:Gadget-OralHistoryRecords-Updated.js: Difference between revisions
Appearance
No edit summary Tag: Reverted |
No edit summary Tag: Reverted |
||
| Line 1: | Line 1: | ||
/* ===== OralHistoryRecords-Updated.js ===== */ | /* ===== OralHistoryRecords-Updated.js (Grouped by Person Key, Option 2) ===== */ | ||
/* global mw */ | /* global mw */ | ||
(function () { | (function () { | ||
'use strict'; | 'use strict'; | ||
// ====== COLUMN CONFIG (MUST MATCH CSV HEADERS EXACTLY) ====== | |||
var COL = { | var COL = { | ||
personKey: 'Person Key', | personKey: 'Person Key', | ||
| Line 30: | Line 31: | ||
function esc(s) { | function esc(s) { | ||
s = (s == null ? '' : String(s)); | |||
return s.replace(/[&<>"']/g, function (c) { | |||
return ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]); | return ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]); | ||
}); | }); | ||
| Line 36: | Line 38: | ||
function norm(s) { | function norm(s) { | ||
return (s || '').toString().toLowerCase(); | return (s || '') | ||
.toString() | |||
.normalize('NFD') | |||
.replace(/[\u0300-\u036f]/g, '') | |||
.toLowerCase(); | |||
} | } | ||
| Line 46: | Line 52: | ||
t = setTimeout(function () { fn.apply(self, a); }, ms); | 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) { | function extractUrls(value) { | ||
if (!value) return []; | if (!value) return []; | ||
var s = String(value); | |||
var re = /https?:\/\/[^\s"'<>()]+/g; | 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; | |||
} | } | ||
| Line 62: | Line 119: | ||
search.type = 'search'; | search.type = 'search'; | ||
search.placeholder = 'Search names, bios…'; | search.placeholder = 'Search names, bios…'; | ||
search.setAttribute('aria-label', 'Search oral history records'); | |||
var count = document.createElement('div'); | var count = document.createElement('div'); | ||
count.id = 'ohr-count'; | count.id = 'ohr-count'; | ||
count.className = 'ohr-count'; | count.className = 'ohr-count'; | ||
count.setAttribute('aria-live', 'polite'); | |||
toolbar.appendChild(search); | toolbar.appendChild(search); | ||
| Line 82: | Line 141: | ||
'<div class="ohr-head">' + | '<div class="ohr-head">' + | ||
'<div class="ohr-titleblock">' + | '<div class="ohr-titleblock">' + | ||
'<h3 class="ohr-name">' + esc(person. | '<h3 class="ohr-name">' + esc(person.first + ' ' + person.last) + '</h3>' + | ||
'</div>' + | '</div>' + | ||
'<button class="ohr-acc-btn" id="' + btnId + '" type="button" aria-expanded="false" aria-controls="' + panelId + '">Details</button>' + | '<button class="ohr-acc-btn" id="' + btnId + '" type="button" aria-expanded="false" aria-controls="' + panelId + '">Details</button>' + | ||
| Line 112: | Line 171: | ||
return div; | return div; | ||
} | |||
function fetchAndRender() { | |||
var root = $('ohr-directory'); | |||
if (!root) return; | |||
buildToolbar(root); | |||
} | } | ||
onReady(function () { | onReady(function () { | ||
if ($('ohr-directory')) { | if ($('ohr-directory')) { | ||
mw.loader.using([]).then(fetchAndRender); | |||
} | } | ||
}); | }); | ||
})(); | })(); | ||
Revision as of 17:43, 22 February 2026
/* ===== 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);
}
});
})();