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.
/**
* Hide leftover imported rows the infobox no longer shows.
* Title-case ALL-CAPS soldier names in category lists (native lists use the real page title).
* Put the research-notes disclaimer under that section heading.
* Split mashed CSV notes into separate bullets, then link MII/IPW team codes.
*/
$(function () {
var NAME_PARTICLES = {
von: 1, van: 1, de: 1, der: 1, den: 1, da: 1, di: 1, du: 1,
la: 1, le: 1, del: 1, della: 1, ter: 1, ten: 1, zu: 1, zum: 1, bin: 1
};
var NAME_SUFFIX = {
jr: 'Jr', 'jr.': 'Jr.', sr: 'Sr', 'sr.': 'Sr.',
esq: 'Esq', 'esq.': 'Esq.', ii: 'II', iii: 'III', iv: 'IV'
};
function lettersOnly(s) {
return String(s || '').replace(/[^A-Za-z]/g, '');
}
function isAllCaps(s) {
var letters = lettersOnly(s);
return letters.length >= 1 && letters === letters.toUpperCase();
}
function isAllLower(s) {
var letters = lettersOnly(s);
return letters.length >= 2 && letters === letters.toLowerCase();
}
function titleCaseWord(s) {
s = String(s || '').toLowerCase();
return s ? s.charAt(0).toUpperCase() + s.slice(1) : s;
}
function prettyCapsWord(word) {
var trail = '';
while (word && /[.,]$/.test(word)) {
trail = word.slice(-1) + trail;
word = word.slice(0, -1);
}
var up = lettersOnly(word).toUpperCase();
if (up.length >= 5 && up.slice(0, 2) === 'MC') {
return 'Mc' + titleCaseWord(up.slice(2)) + trail;
}
if (up.length >= 7 && up.slice(0, 3) === 'MAC') {
return 'Mac' + titleCaseWord(up.slice(3)) + trail;
}
return titleCaseWord(up) + trail;
}
function prettyNameToken(word, leading) {
word = $.trim(word);
if (!word) {
return word;
}
if (/\d/.test(word)) {
return word;
}
if (word.indexOf('-') !== -1 && word.charAt(0) !== '-' && word.slice(-1) !== '-') {
return $.map(word.split('-'), function (part, i) {
return prettyNameToken(part, leading && i === 0);
}).join('-');
}
var apos = word.indexOf("'") !== -1 ? "'" : (word.indexOf('\u2019') !== -1 ? '\u2019' : '');
if (apos) {
var bits = word.split(apos);
return prettyNameToken(bits[0], leading) + apos + prettyNameToken(bits.slice(1).join(apos), false);
}
if (/^(?:[A-Za-z]\.)+$/.test(word)) {
return (isAllCaps(word) || isAllLower(word)) ? word.toUpperCase() : word;
}
var low = word.toLowerCase();
if (NAME_SUFFIX[low]) {
return NAME_SUFFIX[low];
}
var bare = lettersOnly(word);
if (bare.length === 1) {
return bare.toUpperCase() + word.replace(/[A-Za-z]/, '');
}
var lowBare = bare.toLowerCase();
if (NAME_SUFFIX[lowBare] && /^[A-Za-z]+\.?$/.test(word)) {
var canon = NAME_SUFFIX[lowBare];
if (word.slice(-1) === '.' && canon.slice(-1) !== '.') {
return canon + '.';
}
return canon;
}
if (NAME_PARTICLES[lowBare] && /^[A-Za-z]+$/.test(word) && (isAllCaps(word) || isAllLower(word))) {
return leading ? titleCaseWord(lowBare) : lowBare;
}
if (isAllCaps(word) && bare.length >= 2 && bare.length <= 3 && !/[AEIOUY]/i.test(bare)) {
return word;
}
if (isAllCaps(word) || isAllLower(word)) {
return prettyCapsWord(word);
}
return word;
}
function prettyNamePhrase(text, capLead) {
text = $.trim(text || '').replace(/\s*;\s*/g, ' ');
if (!text) {
return '';
}
return $.map(text.split(/\s*,\s*/), function (clause, clauseI) {
clause = $.trim(clause);
if (!clause) {
return null;
}
var words = clause.split(/\s+/);
return $.map(words, function (word, i) {
var particle = lettersOnly(word).toLowerCase();
var leading = (clauseI === 0 && i === 0 && capLead) || words.length === 1;
if (NAME_PARTICLES[particle] && /^[A-Za-z]+$/.test(word) && (isAllCaps(word) || isAllLower(word))) {
return leading ? titleCaseWord(particle) : word.toLowerCase();
}
return prettyNameToken(word, i === 0);
}).join(' ');
}).join(', ');
}
function prettySoldierLabel(text) {
var m = $.trim(text || '').match(/^(.*) \(([^()]+)\)\s*$/);
if (!m || m[1].indexOf('(') !== -1) {
return prettyNamePhrase(text, true);
}
return prettyNamePhrase(m[1], true) + ' (' + m[2] + ')';
}
if (mw.config.get('wgNamespaceNumber') === 14) {
$('.mw-category-group li a').each(function () {
var $a = $(this);
var label = $a.text();
if (!/\(\d/.test(label)) {
return;
}
var pretty = prettySoldierLabel(label);
if (pretty && pretty !== label) {
$a.text(pretty);
$a.attr('title', pretty);
}
});
}
$('.infobox th, .soldier-infobox th, table.wikitable td').each(function () {
var text = $(this).text().replace(/\s+/g, ' ').trim();
if (
text === 'Overseas' ||
text === 'Over Seas Flag' ||
text === 'Special'
) {
$(this).closest('tr').remove();
}
});
var $heading = $('#Research_notes').closest('h2');
var $disclaimer = $('.mw-parser-output > .research-notes-disclaimer').first();
if ($heading.length && $disclaimer.length) {
$disclaimer.insertAfter($heading);
}
function teamHref(name) {
return '/index.php?title=' + encodeURIComponent('Team ' + name).replace(/%20/g, '_');
}
function teamLink(name, label) {
return '<a href="' + teamHref(name) + '" title="Team ' + name + '">' + label + '</a>';
}
function linkifyTeams(raw) {
var text = $('<div>').text(raw).html();
text = text.replace(/\bMII[\s-–]+(540|545|546)\s*[-–]\s*R\b/gi, function (_, num) {
return teamLink('MII ' + num, 'MII ' + num);
});
text = text.replace(/(?<!\bMII[\s-–])\b(540|545|546)\s*[-–]\s*R\b/g, function (_, num) {
return teamLink('MII ' + num, num);
});
text = text.replace(/\bMII[\s-–]+(\d{2,4})\s*[-–]\s*([A-Za-z])\b/gi, function (_, num, letter) {
var name = 'MII ' + num + '-' + letter.toUpperCase();
return teamLink(name, 'MII ' + num + '-' + letter.toUpperCase());
});
text = text.replace(/\bMII[\s-–]+(\d{2,4})\b/gi, function (_, num) {
return teamLink('MII ' + num, 'MII ' + num);
});
text = text.replace(/(?<!\bMII[\s-–])\b([4-5]\d{2})\s*[-–]\s*([A-Za-z])\b/g, function (_, num, letter) {
var name = 'MII ' + num + '-' + letter.toUpperCase();
return teamLink(name, num + '-' + letter.toUpperCase());
});
text = text.replace(/\b(?:IPW\s+)?(?:Italian\s+)?5th\s+class\b/gi, function () {
return teamLink('IPW Italian 5', 'IPW Italian 5');
});
text = text.replace(/\bIPW\s+(\d{1,4})\s*[-–]\s*([A-Za-z])\b/gi, function (_, num, letter) {
var name = 'MII ' + num + '-' + letter.toUpperCase();
return teamLink(name, 'MII ' + num + '-' + letter.toUpperCase());
});
text = text.replace(/\bIPW\s+(\d{1,4})[Tt]?\s*\?/gi, function (_, num) {
return teamLink('IPW ' + num, 'IPW ' + num);
});
text = text.replace(/\bIPW\s+(\d{1,4})[Tt]?\b/gi, function (_, num) {
return teamLink('IPW ' + num, 'IPW ' + num);
});
text = text.replace(/\b(\d{1,4})[Tt]\b/g, function (_, num) {
return teamLink('IPW ' + num, num + 'T');
});
text = text.replace(/\b(\d{1,4})\s*\?/g, function (_, num) {
return teamLink('IPW ' + num, num);
});
return text;
}
function normalizeNoteKey(text) {
text = $.trim(text || '').toLowerCase();
text = text.replace(/^also\s+/, '');
text = text.replace(/[^\w\s\-/]/g, ' ');
return text.replace(/\s+/g, ' ').trim();
}
function dateOnlyFragment(text) {
var m = $.trim(text || '').match(/^(\d{1,2}\/\d{1,2}\/\d{2,4})\.?$/);
return m ? m[1] : '';
}
function joinDateFollowons(parts) {
var out = [];
$.each(parts, function (_, clause) {
clause = $.trim(clause || '');
if (!clause) {
return;
}
var date = dateOnlyFragment(clause);
if (date && out.length) {
var prev = out[out.length - 1].replace(/[ ,;]+$/, '');
if (prev.slice(-date.length) !== date) {
out[out.length - 1] = prev + ', ' + date;
}
return;
}
out.push(clause);
});
return out;
}
function splitNotes(text) {
text = $.trim(text || '');
if (!text) {
return [];
}
text = text.replace(/"+/g, '"');
var parts = [];
$.each(text.split(/[;"]+/), function (_, chunk) {
chunk = $.trim(chunk.replace(/^['"]+|['"]+$/g, ''));
if (!chunk) {
return;
}
if (/https?:\/\//i.test(chunk)) {
parts.push(chunk);
return;
}
chunk = chunk.replace(/(\d{1,2}\/\d{1,2}\/\d{2,4})(?=[A-Za-z])/g, '$1\n');
$.each(chunk.split('\n'), function (__, bit) {
bit = $.trim(bit.replace(/^['"]+|['"]+$/g, ''));
if (!bit) {
return;
}
var dated = bit.match(/^(.*?)\s+(\d{1,2}\/\d{1,2}\/\d{2,4})$/);
if (dated && $.trim(dated[1]) && !/^\d{1,2}\/\d{1,2}\/\d{2,4}$/.test($.trim(dated[1]))) {
parts.push($.trim(dated[1]));
parts.push(dated[2]);
} else {
parts.push(bit);
}
});
});
return joinDateFollowons(parts);
}
var negative = /fail(?:ed|ure)?|flunk|wash(?:ed)?\s*out|kick(?:ed)?\s*out|dismiss|expel|unsatisfactor|poor\s+(?:performance|work)|awol|court[-\s]?martial|disciplin|discharged\s+for\s+cause|drop(?:ped)?\s+(?:out|from)|relieved|eliminat|unfit|misconduct|desert/i;
var $ul = $heading.nextAll('ul').first();
if (!$ul.length) {
return;
}
var items = [];
$ul.find('li').each(function () {
$.each(splitNotes($(this).text()), function (_, clause) {
if (clause) {
items.push(clause);
}
});
});
items = joinDateFollowons(items);
var unique = [];
var seen = {};
$.each(items, function (_, clause) {
var key = normalizeNoteKey(clause);
if (clause && key && !negative.test(clause) && !seen[key]) {
seen[key] = true;
unique.push(clause);
}
});
items = unique;
$ul.empty();
$.each(items, function (_, clause) {
$ul.append($('<li>').html(linkifyTeams(clause)));
});
});
/**
* Soldier page upload box (Gallery section) — profile photos and documents.
*/
$(function () {
if (mw.config.get('wgNamespaceNumber') !== 0 || !$('.soldier-infobox').length) {
return;
}
var PHOTO_EXT = /\.(jpe?g|png|gif|webp)$/i;
var DOC_EXT = /\.(pdf|docx?|txt|rtf|odt|tiff?)$/i;
var ALLOWED = /\.(jpe?g|png|gif|webp|pdf|docx?|txt|rtf|odt|tiff?)$/i;
var $galleryHeading = $('#Gallery').closest('h2');
if (!$galleryHeading.length) {
$galleryHeading = $('h2').filter(function () {
return $.trim($(this).text()).replace(/\[edit\]/i, '') === 'Gallery';
}).first();
}
if (!$galleryHeading.length) {
return;
}
var pageTitle = mw.config.get('wgPageName');
var loggedIn = !!mw.config.get('wgUserName');
var uploadUrl = mw.util.getUrl('Special:Upload') + '&wpRitchieSoldierPage=' + encodeURIComponent(pageTitle);
var $box = $('<div>', { 'class': 'soldier-upload-box' });
$box.append($('<p>').append(
$('<strong>').text('Upload for this soldier'),
document.createTextNode(' — photos (infobox portrait) or documents (PDF, Word, etc.). You can also use '),
$('<a>', { href: uploadUrl, text: 'Special:Upload' }),
document.createTextNode('.')
));
if (!loggedIn) {
$box.append($('<p>', { 'class': 'soldier-upload-login' }).html(
'<a href="' + mw.util.getUrl('Special:UserLogin', { returnto: pageTitle }) + '">Log in</a> to upload files.'
));
$galleryHeading.after($box);
return;
}
var $form = $('<form>', { 'class': 'soldier-upload-form' });
var $file = $('<input>', { type: 'file', name: 'ritchieUploadFile', 'class': 'soldier-upload-file' });
var $profile = $('<label>').append(
$('<input>', { type: 'checkbox', name: 'ritchieProfile', checked: true }),
' Use as profile photo (images only)'
);
var $gallery = $('<label>').append(
$('<input>', { type: 'checkbox', name: 'ritchieGallery' }),
' Add to Gallery (documents, or extra photos)'
);
var $status = $('<p>', { 'class': 'soldier-upload-status', text: '' });
var $submit = $('<button>', { type: 'submit', text: 'Upload file' });
$form.append($file, $profile, $gallery, $submit, $status);
$box.append($form);
$galleryHeading.after($box);
function setStatus(msg, isError) {
$status.text(msg).css('color', isError ? '#ba0000' : '#202122');
}
function refreshChecks() {
var name = ($file.val() || '').split(/[/\\]/).pop();
var isPhoto = PHOTO_EXT.test(name);
var isDoc = DOC_EXT.test(name);
$profile.toggle(isPhoto || !name);
if (isDoc) {
$profile.prop('checked', false);
$gallery.prop('checked', true);
} else if (isPhoto) {
$profile.prop('checked', true);
$gallery.prop('checked', false);
}
}
$file.on('change', refreshChecks);
$form.on('submit', function (evt) {
evt.preventDefault();
var file = $file[0].files[0];
if (!file) {
setStatus('Choose a file first.', true);
return;
}
if (!ALLOWED.test(file.name)) {
setStatus('File type not allowed.', true);
return;
}
var isPhoto = PHOTO_EXT.test(file.name);
var isDoc = DOC_EXT.test(file.name);
var useProfile = $profile.prop('checked') && isPhoto;
var addGallery = $gallery.prop('checked') || isDoc;
if (!useProfile && !addGallery) {
setStatus('Choose profile photo and/or Gallery.', true);
return;
}
$submit.prop('disabled', true);
setStatus('Uploading…');
var api = new mw.Api();
var fileText = (isDoc ? 'Document' : 'Portrait') + ' for [[' + pageTitle + ']].';
api.postWithFormData('csrf', {
action: 'upload',
filename: file.name,
file: file,
comment: 'Uploaded from soldier page',
text: fileText,
format: 'json'
}).then(function (data) {
var uploaded = data.upload && data.upload.filename;
if (!uploaded) {
throw new Error('Upload failed');
}
setStatus('Attaching to page…');
return api.postWithToken('csrf', {
action: 'ritchieattachfile',
filename: uploaded,
soldier: pageTitle,
profile: useProfile ? 1 : 0,
gallery: addGallery ? 1 : 0,
format: 'json'
});
}).done(function () {
setStatus('Done. Reloading…');
window.location.reload();
}).fail(function (code, data) {
var err = (data && data.error && data.error.info) || code || 'Upload failed';
setStatus(err, true);
$submit.prop('disabled', false);
});
});
});