Use mw.util.getUrl for team links (short URLs)
Add Internet Archive click-to-play embeds for interview watchlist
 
(16 intermediate revisions by the same user not shown)
Line 33: Line 33:
{ title: 'Category:Classes', label: 'Classes', icon: 'userGroup' },
{ title: 'Category:Classes', label: 'Classes', icon: 'userGroup' },
{ title: 'Photos', label: 'Photos', icon: 'imageGallery' },
{ title: 'Photos', label: 'Photos', icon: 'imageGallery' },
{ title: 'Training materials', label: 'Training materials', icon: 'articles' },
{ title: 'Contribute', label: 'Contribute', icon: 'edit' },
{ title: 'Contribute', label: 'Contribute', icon: 'edit' },
{ title: 'Contact Us', label: 'Contact Us', icon: 'speechBubbles' },
{ title: 'Contact Us', label: 'Contact Us', icon: 'speechBubbles' },
Line 545: Line 546:
});
});


var $cards = $('.mw-parser-output .yt-interview[data-youtube-id]');
function interviewCardRef($el) {
var yt = String($el.attr('data-youtube-id') || '');
if (yt) {
return { key: 'yt:' + yt, href: '#yt-' + yt };
}
var sc = String($el.attr('data-soundcloud-track') || '');
if (sc) {
return { key: 'sc:' + sc, href: '#sc-' + sc };
}
return null;
}
 
var $cards = $('.mw-parser-output .yt-interview').filter(function () {
return interviewCardRef($(this)) !== null;
});
$cards.each(function (idx) {
$cards.each(function (idx) {
var $card = $(this);
var $card = $(this);
Line 557: Line 572:
}
}
var mine = splitSoldiers($card.attr('data-soldiers'));
var mine = splitSoldiers($card.attr('data-soldiers'));
var selfRef = interviewCardRef($card);
var picks = [];
var picks = [];
var seen = {};
var seen = {};


function addCard($other, why) {
function addCard($other, why) {
var oid = String($other.attr('data-youtube-id') || '');
var ref = interviewCardRef($other);
if (!oid || seen[oid] || oid === $card.attr('data-youtube-id')) {
if (!ref || !selfRef || seen[ref.key] || ref.key === selfRef.key) {
return;
return;
}
}
seen[oid] = 1;
seen[ref.key] = 1;
picks.push({
picks.push({
id: oid,
href: ref.href,
heading: String($other.attr('data-heading') || $other.find('h3, h2').first().text() || 'Interview'),
heading: String($other.attr('data-heading') || $other.find('h3, h2').first().text() || 'Interview'),
short: String($other.attr('data-short') || ''),
short: String($other.attr('data-short') || ''),
Line 592: Line 608:
picks.slice(0, 3).forEach(function (item) {
picks.slice(0, 3).forEach(function (item) {
var label = item.short ? (item.heading + ' — ' + item.short) : item.heading;
var label = item.short ? (item.heading + ' — ' + item.short) : item.heading;
var $a = $('<a></a>').attr('href', '#yt-' + item.id).text(label);
var $a = $('<a></a>').attr('href', item.href).text(label);
var $li = $('<li></li>').append($a);
var $li = $('<li></li>').append($a);
if (item.why === 'same soldier') {
if (item.why === 'same soldier') {
Line 605: Line 621:
}());
}());


(function placeSoldierAwardsSection() {
(function buildSoundCloudEmbeds() {
// Ribbon racks belong in the main column after Biography (like Wikipedia's
var SC_TRACK = /^[0-9]{6,12}$/;
// Awards section). Do not use a wiki == heading here — that stole the TOC.
 
var $out = $('.mw-parser-output').first();
function playSoundCloud($slot, trackId, title) {
var $awards = $out.find('.soldier-awards-section').first();
var src = 'https://w.soundcloud.com/player/?url=' +
if (!$out.length) {
encodeURIComponent('https://api.soundcloud.com/tracks/' + trackId) +
return;
'&color=%23ff5500&auto_play=true&hide_related=true&show_comments=false' +
'&show_user=true&show_reposts=false&show_teaser=false&visual=true';
var $iframe = $('<iframe class="sc-embed-frame" scrolling="no" frameborder="no" allow="autoplay"></iframe>')
.attr({
src: src,
title: title || 'SoundCloud audio',
loading: 'lazy'
});
$slot.empty().append($iframe).addClass('sc-embed-playing');
}
}


function sectionAfter($h) {
$('.mw-parser-output .sc-embed[data-soundcloud-track]').each(function () {
var $nodes = $h.nextUntil('h2');
var $slot = $(this);
if ($nodes.length) {
if ($slot.data('rbScEmbed')) {
return $nodes.last();
return;
}
var trackId = String($slot.attr('data-soundcloud-track') || '');
if (!SC_TRACK.test(trackId)) {
return;
}
}
return $h;
$slot.data('rbScEmbed', 1);
}
var title = String($slot.attr('data-title') || 'SoundCloud audio');
 
var $fallback = $slot.find('.sc-embed-fallback').first();
if ($awards.length && !$awards.data('rbAwardsPlaced')) {
var $btn = $('<button type="button" class="sc-embed-play"></button>')
$awards.data('rbAwardsPlaced', 1);
.attr('aria-label', 'Play ' + title)
var $bio = $out.find('h2').filter(function () {
.append($('<span class="sc-embed-play-icon" aria-hidden="true"></span>'))
var t = $(this).find('.mw-headline').text() || $(this).text();
.append($('<span class="sc-embed-play-text"></span>').text('Play audio'));
return /^(Biography|Life)\b/i.test($.trim(t));
var $poster = $('<div class="sc-embed-poster"></div>').append($btn);
}).first();
if ($fallback.length) {
if ($bio.length) {
$fallback.before($poster);
sectionAfter($bio).after($awards);
$fallback.hide();
} else {
} else {
var $before = $out.find('h2').filter(function () {
$slot.prepend($poster);
var t = $(this).find('.mw-headline').text() || $(this).text();
return /^(Gallery|Research notes|Sources|Technical record)\b/i.test($.trim(t));
}).first();
if ($before.length) {
$before.before($awards);
} else {
$out.append($awards);
}
}
}
$poster.on('click', function (ev) {
ev.preventDefault();
playSoundCloud($slot, trackId, title);
});
});
}());
(function buildAviaryEmbeds() {
var AV_MEDIA = /^[0-9]{4,12}$/;
function playAviary($slot, mediaId, title) {
var src = 'https://milken.aviaryplatform.com/embed/media/' +
encodeURIComponent(mediaId) +
'?embed=true&media_player=true';
var $iframe = $('<iframe class="aviary-embed-frame" allowfullscreen allow="autoplay; encrypted-media; fullscreen; picture-in-picture"></iframe>')
.attr({
src: src,
title: title || 'Aviary media',
loading: 'lazy'
});
$slot.empty().append($iframe).addClass('aviary-embed-playing');
}
}


// Class/team rail rides with awards (after Biography).
$('.mw-parser-output .aviary-embed[data-aviary-media]').each(function () {
var $related = $out.find('.soldier-related').first();
var $slot = $(this);
if ($related.length && !$related.data('rbRelatedPlaced')) {
if ($slot.data('rbAviaryEmbed')) {
$related.data('rbRelatedPlaced', 1);
return;
var $awardsNow = $out.find('.soldier-awards-section').first();
}
if ($awardsNow.length) {
var mediaId = String($slot.attr('data-aviary-media') || '');
$awardsNow.after($related);
if (!AV_MEDIA.test(mediaId)) {
return;
}
$slot.data('rbAviaryEmbed', 1);
var title = String($slot.attr('data-title') || 'Aviary media');
var playLabel = String($slot.attr('data-play-label') || 'Play video');
var $fallback = $slot.find('.aviary-embed-fallback').first();
var $btn = $('<button type="button" class="aviary-embed-play"></button>')
.attr('aria-label', playLabel + ' — ' + title)
.append($('<span class="aviary-embed-play-icon" aria-hidden="true"></span>'))
.append($('<span class="aviary-embed-play-text"></span>').text(playLabel));
var $poster = $('<div class="aviary-embed-poster"></div>').append($btn);
if ($fallback.length) {
$fallback.before($poster);
$fallback.hide();
} else {
} else {
var $bio2 = $out.find('h2').filter(function () {
$slot.prepend($poster);
var t = $(this).find('.mw-headline').text() || $(this).text();
return /^(Biography|Life)\b/i.test($.trim(t));
}).first();
if ($bio2.length) {
sectionAfter($bio2).after($related);
}
}
}
$poster.on('click', function (ev) {
ev.preventDefault();
playAviary($slot, mediaId, title);
});
});
}());
(function buildInternetArchiveEmbeds() {
// archive.org item identifiers (e.g. RobertA.Potash13january2012YiddishBookCenter)
var IA_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{5,120}$/;
function playInternetArchive($slot, itemId, title) {
var src = 'https://archive.org/embed/' + encodeURIComponent(itemId);
var $iframe = $('<iframe class="ia-embed-frame" allowfullscreen allow="autoplay; encrypted-media; fullscreen; picture-in-picture"></iframe>')
.attr({
src: src,
title: title || 'Internet Archive media',
loading: 'lazy'
});
$slot.empty().append($iframe).addClass('ia-embed-playing');
}
}


// Story cue belongs under the lead (above Biography).
$('.mw-parser-output .ia-embed[data-archive-id]').each(function () {
var $storyCue = $out.find('.soldier-story-cue').first();
var $slot = $(this);
if ($storyCue.length && !$storyCue.data('rbStoryCuePlaced')) {
if ($slot.data('rbIaEmbed')) {
$storyCue.data('rbStoryCuePlaced', 1);
return;
var $leadCue = $out.find('.soldier-lead, .page-lead').first();
}
if ($leadCue.length) {
var itemId = String($slot.attr('data-archive-id') || '');
$leadCue.after($storyCue);
if (!IA_ID.test(itemId)) {
return;
}
}
}
$slot.data('rbIaEmbed', 1);
 
var title = String($slot.attr('data-title') || 'Internet Archive media');
// Interview / film cue sits with the story cue under the lead.
var playLabel = String($slot.attr('data-play-label') || 'Play interview');
var $mediaCue = $out.find('.soldier-media-cue').first();
var $fallback = $slot.find('.ia-embed-fallback').first();
if ($mediaCue.length && !$mediaCue.data('rbMediaCuePlaced')) {
var $btn = $('<button type="button" class="ia-embed-play"></button>')
$mediaCue.data('rbMediaCuePlaced', 1);
.attr('aria-label', playLabel + ' — ' + title)
var $afterStory = $out.find('.soldier-story-cue').first();
.append($('<span class="ia-embed-play-icon" aria-hidden="true"></span>'))
if ($afterStory.length) {
.append($('<span class="ia-embed-play-text"></span>').text(playLabel));
$afterStory.after($mediaCue);
var $poster = $('<div class="ia-embed-poster"></div>').append($btn);
if ($fallback.length) {
$fallback.before($poster);
$fallback.hide();
} else {
} else {
var $leadMedia = $out.find('.soldier-lead, .page-lead').first();
$slot.prepend($poster);
if ($leadMedia.length) {
$leadMedia.after($mediaCue);
}
}
}
}
$poster.on('click', function (ev) {
 
ev.preventDefault();
// Contents box belongs after the lead on soldier/profile pages only.
playInternetArchive($slot, itemId, title);
// Other pages (e.g. Site changelog) keep MediaWiki's default TOC placement.
});
var $toc = $out.find('#toc, .toc, .mw-table-of-contents').first();
});
if ($toc.length && !$toc.data('rbTocPlaced')) {
$toc.data('rbTocPlaced', 1);
var $lead = $out.find('.soldier-lead, .page-lead, .home-lead').first();
if ($lead.length) {
$lead.after($toc);
} else {
var $ibox = $out.find('table.soldier-infobox, table.infobox').first();
if ($ibox.length) {
$ibox.after($toc);
}
}
}
}());
}());


(function moveSoldierFooterCues() {
(function placeSoldierAwardsSection() {
// Ribbon racks belong in the main column after Biography (like Wikipedia's
// Awards section). Do not use a wiki == heading here — that stole the TOC.
var $out = $('.mw-parser-output').first();
var $out = $('.mw-parser-output').first();
var $awards = $out.find('.soldier-awards-section').first();
if (!$out.length) {
if (!$out.length) {
return;
return;
}
}
var $notes = $out.find('.research-notes-disclaimer').first();
 
var $sources = $out.find('.source-confidence-cue').first();
function sectionAfter($h) {
if (!$notes.length && !$sources.length) {
var $nodes = $h.nextUntil('h2');
return;
if ($nodes.length) {
}
return $nodes.last();
var $footer = $out.find('.soldier-page-footer-notes').first();
}
if (!$footer.length) {
return $h;
$footer = $('<div class="soldier-page-footer-notes"></div>');
$out.append($footer);
}
if ($notes.length) {
$footer.append($notes);
}
if ($sources.length) {
$footer.append($sources);
}
}
}());


var NAME_PARTICLES = {
if ($awards.length && !$awards.data('rbAwardsPlaced')) {
von: 1, van: 1, de: 1, der: 1, den: 1, da: 1, di: 1, du: 1,
$awards.data('rbAwardsPlaced', 1);
la: 1, le: 1, del: 1, della: 1, ter: 1, ten: 1, zu: 1, zum: 1, bin: 1
var $bio = $out.find('h2').filter(function () {
};
var t = $(this).find('.mw-headline').text() || $(this).text();
var NAME_SUFFIX = {
return /^(Biography|Life)\b/i.test($.trim(t));
jr: 'Jr', 'jr.': 'Jr.', sr: 'Sr', 'sr.': 'Sr.',
}).first();
esq: 'Esq', 'esq.': 'Esq.', ii: 'II', iii: 'III', iv: 'IV'
if ($bio.length) {
};
sectionAfter($bio).after($awards);
function lettersOnly(s) {
} else {
return String(s || '').replace(/[^A-Za-z]/g, '');
var $before = $out.find('h2').filter(function () {
}
var t = $(this).find('.mw-headline').text() || $(this).text();
function isAllCaps(s) {
return /^(Gallery|Research notes|Sources|Technical record)\b/i.test($.trim(t));
var letters = lettersOnly(s);
}).first();
return letters.length >= 1 && letters === letters.toUpperCase();
if ($before.length) {
}
$before.before($awards);
function isAllLower(s) {
} else {
var letters = lettersOnly(s);
$out.append($awards);
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') {
// Class/team rail rides with awards (after Biography).
return 'Mc' + titleCaseWord(up.slice(2)) + trail;
var $related = $out.find('.soldier-related').first();
}
if ($related.length && !$related.data('rbRelatedPlaced')) {
if (up.length >= 7 && up.slice(0, 3) === 'MAC') {
$related.data('rbRelatedPlaced', 1);
return 'Mac' + titleCaseWord(up.slice(3)) + trail;
var $awardsNow = $out.find('.soldier-awards-section').first();
if ($awardsNow.length) {
$awardsNow.after($related);
} else {
var $bio2 = $out.find('h2').filter(function () {
var t = $(this).find('.mw-headline').text() || $(this).text();
return /^(Biography|Life)\b/i.test($.trim(t));
}).first();
if ($bio2.length) {
sectionAfter($bio2).after($related);
}
}
}
}
return titleCaseWord(up) + trail;
 
}
// Story cue belongs under the lead (above Biography).
function prettyNameToken(word, leading) {
var $storyCue = $out.find('.soldier-story-cue').first();
word = $.trim(word);
if ($storyCue.length && !$storyCue.data('rbStoryCuePlaced')) {
if (!word) {
$storyCue.data('rbStoryCuePlaced', 1);
return word;
var $leadCue = $out.find('.soldier-lead, .page-lead').first();
if ($leadCue.length) {
$leadCue.after($storyCue);
}
}
}
if (/\d/.test(word)) {
 
return word;
// Interview / film cue sits with the story cue under the lead.
}
var $mediaCue = $out.find('.soldier-media-cue').first();
if (word.indexOf('-') !== -1 && word.charAt(0) !== '-' && word.slice(-1) !== '-') {
if ($mediaCue.length && !$mediaCue.data('rbMediaCuePlaced')) {
return $.map(word.split('-'), function (part, i) {
$mediaCue.data('rbMediaCuePlaced', 1);
return prettyNameToken(part, leading && i === 0);
var $afterStory = $out.find('.soldier-story-cue').first();
}).join('-');
if ($afterStory.length) {
$afterStory.after($mediaCue);
} else {
var $leadMedia = $out.find('.soldier-lead, .page-lead').first();
if ($leadMedia.length) {
$leadMedia.after($mediaCue);
}
}
}
}
var apos = word.indexOf("'") !== -1 ? "'" : (word.indexOf('\u2019') !== -1 ? '\u2019' : '');
 
if (apos) {
// Contents box belongs after the lead on soldier/profile pages only.
var bits = word.split(apos);
// Other pages (e.g. Site changelog) keep MediaWiki's default TOC placement.
return prettyNameToken(bits[0], leading) + apos + prettyNameToken(bits.slice(1).join(apos), false);
var $toc = $out.find('#toc, .toc, .mw-table-of-contents').first();
if ($toc.length && !$toc.data('rbTocPlaced')) {
$toc.data('rbTocPlaced', 1);
var $lead = $out.find('.soldier-lead, .page-lead, .home-lead').first();
if ($lead.length) {
$lead.after($toc);
} else {
var $ibox = $out.find('table.soldier-infobox, table.infobox').first();
if ($ibox.length) {
$ibox.after($toc);
}
}
}
}
if (/^(?:[A-Za-z]\.)+$/.test(word)) {
}());
return (isAllCaps(word) || isAllLower(word)) ? word.toUpperCase() : word;
 
}
(function moveSoldierFooterCues() {
var low = word.toLowerCase();
var $out = $('.mw-parser-output').first();
if (NAME_SUFFIX[low]) {
if (!$out.length) {
return NAME_SUFFIX[low];
return;
}
}
var bare = lettersOnly(word);
var $notes = $out.find('.research-notes-disclaimer').first();
if (bare.length === 1) {
var $sources = $out.find('.source-confidence-cue').first();
return bare.toUpperCase() + word.replace(/[A-Za-z]/, '');
if (!$notes.length && !$sources.length) {
return;
}
}
var lowBare = bare.toLowerCase();
var $footer = $out.find('.soldier-page-footer-notes').first();
if (NAME_SUFFIX[lowBare] && /^[A-Za-z]+\.?$/.test(word)) {
if (!$footer.length) {
var canon = NAME_SUFFIX[lowBare];
$footer = $('<div class="soldier-page-footer-notes"></div>');
if (word.slice(-1) === '.' && canon.slice(-1) !== '.') {
$out.append($footer);
return canon + '.';
}
return canon;
}
}
if (NAME_PARTICLES[lowBare] && /^[A-Za-z]+$/.test(word) && (isAllCaps(word) || isAllLower(word))) {
if ($notes.length) {
return leading ? titleCaseWord(lowBare) : lowBare;
$footer.append($notes);
}
}
if (isAllCaps(word) && bare.length >= 2 && bare.length <= 3 && !/[AEIOUY]/i.test(bare)) {
if ($sources.length) {
return word;
$footer.append($sources);
}
}
if (isAllCaps(word) || isAllLower(word)) {
}());
return prettyCapsWord(word);
 
}
var NAME_PARTICLES = {
return word;
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 prettyNamePhrase(text, capLead) {
function prettyCapsWord(word) {
text = $.trim(text || '').replace(/\s*;\s*/g, ' ');
var trail = '';
if (!text) {
while (word && /[.,]$/.test(word)) {
return '';
trail = word.slice(-1) + trail;
word = word.slice(0, -1);
}
}
return $.map(text.split(/\s*,\s*/), function (clause, clauseI) {
var up = lettersOnly(word).toUpperCase();
clause = $.trim(clause);
if (up.length >= 5 && up.slice(0, 2) === 'MC') {
if (!clause) {
return 'Mc' + titleCaseWord(up.slice(2)) + trail;
return null;
}
}
if (up.length >= 7 && up.slice(0, 3) === 'MAC') {
var words = clause.split(/\s+/);
return 'Mac' + titleCaseWord(up.slice(3)) + trail;
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] + ')';
return titleCaseWord(up) + trail;
}
}
function maybePrettySoldierTitle(label) {
function prettyNameToken(word, leading) {
var text = $.trim(label || '');
word = $.trim(word);
if (!text || text.indexOf(',') === -1) {
if (!word) {
return text;
return word;
}
}
var sur = text.split(',')[0];
if (/\d/.test(word)) {
var letters = lettersOnly(sur);
return word;
if (!(letters.length >= 2 && letters === letters.toUpperCase())) {
return text;
}
}
return prettySoldierLabel(text);
if (word.indexOf('-') !== -1 && word.charAt(0) !== '-' && word.slice(-1) !== '-') {
}
return $.map(word.split('-'), function (part, i) {
function applyPrettySoldierLabels($links) {
return prettyNameToken(part, leading && i === 0);
$links.each(function () {
}).join('-');
var $a = $(this);
}
var label = $a.text();
var apos = word.indexOf("'") !== -1 ? "'" : (word.indexOf('\u2019') !== -1 ? '\u2019' : '');
var pretty = maybePrettySoldierTitle(label);
if (apos) {
if (pretty && pretty !== label) {
var bits = word.split(apos);
$a.text(pretty);
return prettyNameToken(bits[0], leading) + apos + prettyNameToken(bits.slice(1).join(apos), false);
if ($a.attr('title') === label || !$a.attr('title')) {
}
$a.attr('title', pretty);
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 (mw.config.get('wgNamespaceNumber') === 14) {
if (NAME_PARTICLES[lowBare] && /^[A-Za-z]+$/.test(word) && (isAllCaps(word) || isAllLower(word))) {
applyPrettySoldierLabels($('.mw-category-group li a'));
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;
}
}
if (mw.config.get('wgCanonicalSpecialPageName') === 'Search') {
function prettyNamePhrase(text, capLead) {
applyPrettySoldierLabels($('.mw-search-result-heading a'));
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(', ');
}
}
// Search box suggestions use the real page title; pretty-case ALL-CAPS surnames as they appear.
function prettySoldierLabel(text) {
(function watchSearchSuggestions() {
var m = $.trim(text || '').match(/^(.*) \(([^()]+)\)\s*$/);
function polish() {
if (!m || m[1].indexOf('(') !== -1) {
$('.suggestions-result, .suggestions-result a, .suggestions-special a, .cdx-menu-item, .mw-searchSuggest-link').each(function () {
return prettyNamePhrase(text, true);
var $el = $(this);
var label = $.trim($el.text() || '');
if (rbIsMergedDuplicateTitle(label)) {
$el.closest('li, .cdx-menu-item, .suggestions-result').addBack().filter('li, .cdx-menu-item, .suggestions-result').first().hide();
}
});
applyPrettySoldierLabels(
$('.suggestions-result a, .suggestions-special a, .cdx-menu-item__text__label, .mw-searchSuggest-link')
);
$('.suggestions-result, .cdx-menu-item__text__label').each(function () {
var $el = $(this);
if ($el.is('a')) {
return;
}
var label = $el.text();
var pretty = maybePrettySoldierTitle(label);
if (pretty && pretty !== label) {
$el.text(pretty);
}
});
}
}
var root = document.body;
return prettyNamePhrase(m[1], true) + ' (' + m[2] + ')';
if (!root || !window.MutationObserver) {
}
return;
function maybePrettySoldierTitle(label) {
var text = $.trim(label || '');
if (!text || text.indexOf(',') === -1) {
return text;
}
}
var obs = new MutationObserver(function () {
var sur = text.split(',')[0];
polish();
var letters = lettersOnly(sur);
if (!(letters.length >= 2 && letters === letters.toUpperCase())) {
return text;
}
return prettySoldierLabel(text);
}
function applyPrettySoldierLabels($links) {
$links.each(function () {
var $a = $(this);
var label = $a.text();
var pretty = maybePrettySoldierTitle(label);
if (pretty && pretty !== label) {
$a.text(pretty);
if ($a.attr('title') === label || !$a.attr('title')) {
$a.attr('title', pretty);
}
}
});
});
obs.observe(root, { childList: true, subtree: true });
}());
$('.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();
}
});
function teamHref(name) {
return mw.util.getUrl('Team ' + name);
}
}
function teamLink(name, label) {
if (mw.config.get('wgNamespaceNumber') === 14) {
return '<a href="' + teamHref(name) + '" title="Team ' + name + '">' + label + '</a>';
applyPrettySoldierLabels($('.mw-category-group li a'));
}
}
function linkifyTeams(raw) {
if (mw.config.get('wgCanonicalSpecialPageName') === 'Search') {
var text = $('<div>').text(raw).html();
applyPrettySoldierLabels($('.mw-search-result-heading a'));
text = text.replace(/\bMII[\s-–]+(540|545|546)\s*[-–]\s*R\b/gi, function (_, num) {
}
return teamLink('MII ' + num, 'MII ' + num);
// Search box suggestions use the real page title; pretty-case ALL-CAPS surnames as they appear.
(function watchSearchSuggestions() {
function polish() {
$('.suggestions-result, .suggestions-result a, .suggestions-special a, .cdx-menu-item, .mw-searchSuggest-link').each(function () {
var $el = $(this);
var label = $.trim($el.text() || '');
if (rbIsMergedDuplicateTitle(label)) {
$el.closest('li, .cdx-menu-item, .suggestions-result').addBack().filter('li, .cdx-menu-item, .suggestions-result').first().hide();
}
});
applyPrettySoldierLabels(
$('.suggestions-result a, .suggestions-special a, .cdx-menu-item__text__label, .mw-searchSuggest-link')
);
$('.suggestions-result, .cdx-menu-item__text__label').each(function () {
var $el = $(this);
if ($el.is('a')) {
return;
}
var label = $el.text();
var pretty = maybePrettySoldierTitle(label);
if (pretty && pretty !== label) {
$el.text(pretty);
}
});
}
var root = document.body;
if (!root || !window.MutationObserver) {
return;
}
var obs = new MutationObserver(function () {
polish();
});
});
text = text.replace(/(?<!\bMII[\s-–])\b(540|545|546)\s*[-–]\s*R\b/g, function (_, num) {
obs.observe(root, { childList: true, subtree: true });
return teamLink('MII ' + num, num);
}());
 
$('.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();
}
});
 
function teamHref(name) {
return mw.util.getUrl('Team ' + name);
}
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) {
text = text.replace(/\bMII[\s-–]+(\d{2,4})\s*[-–]\s*([A-Za-z])\b/gi, function (_, num, letter) {
Line 1,030: Line 1,186:
}
}


var negative = /fail(?:ed|ure)?|flunk|wash(?:ed)?\s*out|kick(?:ed)?\s*out|dismiss|expel|unsatisfactor|poor\s+(?:performance|work|grades)|awol|court[-\s]?martial|disciplin|discharged\s+for\s+cause|drop(?:ped)?\s+(?:out|from)|relieved|eliminat|unfit|misconduct|desert|excess\s+absence|absence\s*\(|\billness\b|sick\s+call|medical\s+board|psychiatr|neurot|homosexual|suicid|alcohol(?:ism)?|drunk|intoxicat|venereal|\bVD\b|syphili|gonorrh|mental\s+(?:case|illness)|nervous\s+breakdown|not\s+graduat|did\s+not\s+graduate|physically\s+unfit|unfit\s+for|non[-\s]?graduate|certificate\s+listed\s+as\s+ng/i;
var negative = /fail(?:ed|ure)?|flunk|wash(?:ed)?\s*out|kick(?:ed)?\s*out|dismiss|expel|unsatisfactor|poor\s+(?:performance|work|grades)|awol|court[-\s]?martial|disciplin|discharged\s+for\s+cause|drop(?:ped)?\s+(?:out|from)|relieved|eliminat|unfit|misconduct|desert|excess\s+absence|absence\s*\(|\billness\b|sick\s+call|medical\s+board|psychiatr|neurot|homosexual|suicid|alcohol(?:ism)?|drunk|intoxicat|venereal|\bVD\b|syphili|gonorrh|mental\s+(?:case|illness)|nervous\s+breakdown|not\s+graduat|did\s+not\s+graduate|physically\s+unfit|unfit\s+for|non[-\s]?graduate|certificate\s+listed\s+as\s+ng|(?:^|\b)NG\b(?:\s+Class|\s+OB|\s*$|\s*[,;:/]|\s+\d)|\bnongrad/i;
var $ul = $heading.nextAll('ul').first();
var $ul = $heading.nextAll('ul').first();
if (!$ul.length) {
if (!$ul.length) {
Line 1,273: Line 1,429:
{ key: 'death_year', label: 'Death year', hint: 'Four digits, e.g. 2001' },
{ key: 'death_year', label: 'Death year', hint: 'Four digits, e.g. 2001' },
{ key: 'nativity', label: 'Nativity (birthplace)' },
{ key: 'nativity', label: 'Nativity (birthplace)' },
{ key: 'religion', label: 'Religion' },
{ key: 'religion', label: 'Religion' }
{ key: 'marital', label: 'Marital status' },
{ key: 'education', label: 'Education' },
{ key: 'occupation', label: 'Civilian occupation' },
{ key: 'state', label: 'State (residence)' }
]
]
},
},
Line 1,299: Line 1,451:
collapsed: true,
collapsed: true,
fields: [
fields: [
{ key: 'other_class', label: 'Other class' },
{
key: 'other_class',
label: 'Other class',
hint: 'Roster code, e.g. SC8, DC36, MITU4 (shown expanded on the page)'
},
{ key: 'division', label: 'Unit / division' },
{ key: 'division', label: 'Unit / division' },
{ key: 'loc', label: 'LOC' },
{ key: 'loc', label: 'LOC' }
{ key: 'date_arrive', label: 'Date arrive' },
{ key: 'date_depart', label: 'Date depart' }
]
]
}
}
Line 3,168: Line 3,322:
var $block = $(this);
var $block = $(this);
var cat = String($block.attr('data-team-cat') || '');
var cat = String($block.attr('data-team-cat') || '');
if (!/^Category:Team /.test(cat)) {
if (!/^Category:Team /.test(cat)) {
return;
return;
}
}
var $links = $block.find('.soldier-related-links-live').first();
var $links = $block.find('.soldier-related-links-live').first();
if (!$links.length) {
if (!$links.length) {
return;
return;
}
}
$links.text('Loading teammates…');
$links.text('Loading teammates…');
fetchCategoryMembers(cat).done(function (titles) {
fetchCategoryMembers(cat).done(function (titles) {
var others = titles.filter(function (t) {
var others = titles.filter(function (t) {
return t.replace(/_/g, ' ') !== current;
return t.replace(/_/g, ' ') !== current;
});
});
others.sort(function (a, b) {
others.sort(function (a, b) {
return titleToCaption(a).localeCompare(titleToCaption(b));
return titleToCaption(a).localeCompare(titleToCaption(b));
});
});
$links.empty();
$links.empty();
if (!others.length) {
if (!others.length) {
$links.append(document.createTextNode('No other teammates listed yet · '));
$links.append(document.createTextNode('No other teammates listed yet · '));
$links.append(wikiLink(cat, 'full roster'));
$links.append(wikiLink(cat, 'full roster'));
return;
return;
}
}
var max = 24;
var max = 24;
others.slice(0, max).forEach(function (title, i) {
others.slice(0, max).forEach(function (title, i) {
if (i > 0) {
if (i > 0) {
$links.append(document.createTextNode(' · '));
$links.append(document.createTextNode(' · '));
}
}
$links.append(wikiLink(title, titleToCaption(title)));
$links.append(wikiLink(title, titleToCaption(title)));
});
});
$links.append(document.createTextNode(' · '));
$links.append(document.createTextNode(' · '));
$links.append(wikiLink(cat, others.length > max ? 'full roster (' + others.length + ')' : 'full roster'));
$links.append(wikiLink(cat, others.length > max ? 'full roster (' + others.length + ')' : 'full roster'));
}).fail(function () {
}).fail(function () {
$links.empty().append(wikiLink(cat, 'Browse team roster'));
$links.empty().append(wikiLink(cat, 'Browse team roster'));
});
});
});
});
});
});
}());
}());
 
$(function rbCampManualUpload() {
var $slot = $('.mw-parser-output .camp-manual-upload-slot').first();
if (!$slot.length || $slot.data('rbCampManualUpload')) {
return;
}
$slot.data('rbCampManualUpload', 1);
 
var LIBRARY = 'Training materials/Library';
var PLACEHOLDER = "''No manuals listed yet. Upload a PDF above and it will appear here.''";
var DOC_EXT = /\.(pdf|docx?|txt|rtf|odt|tiff?|jpe?g|png|gif|webp)$/i;
var pageTitle = mw.config.get('wgPageName');
var loggedIn = !!mw.config.get('wgUserName');
var uploadUrl = mw.util.getUrl('Special:Upload');
 
$slot.empty();
var $box = $('<div>', { 'class': 'camp-manual-upload-box soldier-upload-body' });
$box.append($('<p>', { 'class': 'soldier-contrib-help' }).append(
document.createTextNode('Upload a manual or related Camp Ritchie document (PDF preferred). Also '),
$('<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.'
));
$slot.append($box);
return;
}
 
var $form = $('<form>', { 'class': 'soldier-upload-form camp-manual-upload-form' });
var $file = $('<input>', {
type: 'file',
name: 'campManualFile',
'class': 'soldier-upload-file',
accept: '.pdf,.doc,.docx,.txt,.rtf,.odt,.tif,.tiff,.jpg,.jpeg,.png'
});
var $label = $('<label>').append(
document.createTextNode('Display title (optional)'),
$('<input>', {
type: 'text',
name: 'campManualTitle',
'class': 'camp-manual-title',
placeholder: 'e.g. Interrogation manual, 1943',
maxlength: 200
})
);
var $note = $('<label>').append(
document.createTextNode('Short note / source (optional)'),
$('<input>', {
type: 'text',
name: 'campManualNote',
'class': 'camp-manual-note',
placeholder: 'e.g. NARA / museum scan',
maxlength: 300
})
);
var $status = $('<p>', { 'class': 'soldier-upload-status', text: '' });
var $progress = $('<div>', { 'class': 'camp-manual-progress', hidden: true });
var $progressFill = $('<div>', { 'class': 'camp-manual-progress-fill' });
var $progressBar = $('<div>', {
'class': 'camp-manual-progress-bar',
role: 'progressbar',
'aria-valuemin': 0,
'aria-valuemax': 100,
'aria-valuenow': 0
}).append($progressFill);
var $progressLabel = $('<p>', { 'class': 'camp-manual-progress-label', text: '' });
$progress.append($progressBar, $progressLabel);
var $submit = $('<button>', { type: 'submit', text: 'Upload to library' });
$form.append($file, $label, $note, $submit, $progress, $status);
$box.append($form);
$slot.append($box);
 
function setStatus(msg, isError) {
$status.text(msg).css('color', isError ? '#ba0000' : '#202122');
}
 
function formatBytes(n) {
if (n >= 1024 * 1024) {
return (n / (1024 * 1024)).toFixed(1) + ' MB';
}
if (n >= 1024) {
return Math.round(n / 1024) + ' KB';
}
return n + ' B';
}
 
function setProgress(loaded, total) {
$progress.prop('hidden', false);
if (!total) {
$progress.addClass('is-indeterminate');
$progressBar.attr('aria-valuenow', 0);
$progressFill.css('width', '30%');
$progressLabel.text('Uploading…');
return;
}
$progress.removeClass('is-indeterminate');
var pct = Math.max(0, Math.min(100, Math.round((loaded / total) * 100)));
$progressBar.attr('aria-valuenow', pct);
$progressFill.css('width', pct + '%');
$progressLabel.text(pct + '% · ' + formatBytes(loaded) + ' of ' + formatBytes(total));
}
 
function resetProgress() {
$progress.prop('hidden', true).removeClass('is-indeterminate');
$progressFill.css('width', '0%');
$progressBar.attr('aria-valuenow', 0);
$progressLabel.text('');
}
 
function escapePipe(s) {
return String(s || '').replace(/\|/g, '–').replace(/\[\[/g, '').replace(/\]\]/g, '');
}
 
function buildBullet(filename, displayTitle, note) {
var label = escapePipe(displayTitle || filename.replace(/_/g, ' '));
var line = '* [[Media:' + filename + '|' + label + ']]';
if (note) {
line += ' — ' + escapePipe(note);
}
return line;
}
 
function appendToLibrary(api, filename, displayTitle, note) {
var bullet = buildBullet(filename, displayTitle, note);
return api.get({
action: 'query',
prop: 'revisions|info',
titles: LIBRARY,
rvprop: 'content|timestamp',
rvslots: 'main',
curtimestamp: 1,
formatversion: 2,
maxage: 0,
smaxage: 0
}).then(function (data) {
var page = data.query && data.query.pages && data.query.pages[0];
var rev = page && page.revisions && page.revisions[0];
var text = (rev && rev.slots && rev.slots.main && rev.slots.main.content) || '';
var basets = rev && rev.timestamp;
var startts = data.curtimestamp;
var next;
var trimmed = $.trim(text);
var already = trimmed.indexOf('[[Media:' + filename + '|') !== -1 ||
trimmed.indexOf('[[File:' + filename + '|') !== -1;
if (already) {
return purgeLibraryPages(api).then(function () {
return { edit: { result: 'Success', nochange: true } };
});
}
if (!trimmed || trimmed.indexOf(PLACEHOLDER) !== -1) {
next = bullet + '\n';
} else {
next = trimmed + '\n' + bullet + '\n';
}
var params = {
action: 'edit',
title: LIBRARY,
text: next,
summary: 'Add training material: ' + filename,
format: 'json'
};
if (basets) {
params.basetimestamp = basets;
}
if (startts) {
params.starttimestamp = startts;
}
return api.postWithToken('csrf', params).then(function (editData) {
if (editData.edit && editData.edit.result === 'Success') {
return purgeLibraryPages(api).then(function () {
return editData;
});
}
if (editData.error && editData.error.code === 'editconflict') {
return appendToLibrary(api, filename, displayTitle, note);
}
throw new Error((editData.error && editData.error.info) || 'Could not update library list');
});
});
}
 
function failMessage(code, data) {
if (data && data.error && data.error.info) {
return data.error.info;
}
if (data && data.exception && data.exception.message) {
return data.exception.message;
}
if (code && typeof code === 'object' && code.message) {
return code.message;
}
if (typeof code === 'string' && code && code !== 'http' && code !== 'error' && code !== 'timeout') {
return code;
}
if (data && data.xhr && data.xhr.status) {
return 'Upload failed (HTTP ' + data.xhr.status + '). Try again, or use Special:Upload.';
}
return 'Upload failed (connection dropped or timed out). Try again, or use Special:Upload.';
}
 
function purgeLibraryPages(api) {
return api.postWithToken('csrf', {
action: 'purge',
titles: 'Training materials|' + LIBRARY,
forcelinkupdate: 1,
format: 'json'
});
}
 
function readLibraryText(api) {
return api.get({
action: 'query',
prop: 'revisions|info',
titles: LIBRARY,
rvprop: 'content|timestamp',
rvslots: 'main',
curtimestamp: 1,
formatversion: 2,
maxage: 0,
smaxage: 0
}).then(function (data) {
var page = data.query && data.query.pages && data.query.pages[0];
var rev = page && page.revisions && page.revisions[0];
return {
text: (rev && rev.slots && rev.slots.main && rev.slots.main.content) || '',
basetimestamp: rev && rev.timestamp,
starttimestamp: data.curtimestamp
};
});
}
 
function writeLibraryText(api, next, summary, basets, startts) {
var params = {
action: 'edit',
title: LIBRARY,
text: next,
summary: summary,
format: 'json'
};
if (basets) {
params.basetimestamp = basets;
}
if (startts) {
params.starttimestamp = startts;
}
return api.postWithToken('csrf', params).then(function (editData) {
if (editData.edit && editData.edit.result === 'Success') {
return purgeLibraryPages(api).then(function () {
return editData;
});
}
if (editData.error && editData.error.code === 'editconflict') {
throw new Error('editconflict');
}
throw new Error((editData.error && editData.error.info) || 'Could not update library list');
});
}
 
function removeFromLibrary(api, filename) {
return readLibraryText(api).then(function (cur) {
var lines = String(cur.text || '').split(/\r?\n/);
var nextLines = lines.filter(function (line) {
return line.indexOf('[[Media:' + filename + '|') === -1 &&
line.indexOf('[[File:' + filename + '|') === -1 &&
line.indexOf(filename) === -1;
});
var next = $.trim(nextLines.join('\n'));
if (!next) {
next = PLACEHOLDER;
} else {
next = next + '\n';
}
return writeLibraryText(
api,
next,
'Remove from Training materials library: ' + filename,
cur.basetimestamp,
cur.starttimestamp
).then(null, function (err) {
if (String(err && err.message || err) === 'editconflict') {
return removeFromLibrary(api, filename);
}
throw err;
});
});
}
 
function deleteLibraryFile(api, filename) {
return api.postWithToken('csrf', {
action: 'delete',
title: 'File:' + filename,
reason: 'Removed from Training materials library',
format: 'json'
}).then(function (data) {
if (data.error) {
throw new Error(data.error.info || 'Could not delete file');
}
return removeFromLibrary(api, filename);
});
}
 
function filenameFromHref(href, titleAttr) {
if (titleAttr && /\.[a-z0-9]{2,5}$/i.test(titleAttr)) {
return String(titleAttr).replace(/ /g, '_');
}
if (!href) {
return '';
}
try {
href = decodeURIComponent(href);
} catch (e) { /* ignore */ }
var m = href.match(/\/wiki\/(?:File|Media):([^?#]+)/i) ||
href.match(/[?&]title=(?:File|Media):([^&#]+)/i) ||
href.match(/\/images\/(?:thumb\/)?[^/]+\/[^/]+\/([^/?#]+)$/i);
if (!m) {
return '';
}
return m[1].replace(/_/g, ' ').trim().replace(/ /g, '_');
}
 
function enhanceLibraryManage() {
if (!mw.config.get('wgUserName')) {
return;
}
var $heading = $('.mw-parser-output .mw-heading, .mw-parser-output h2').filter(function () {
return /^Library\b/i.test($(this).text().replace(/\s*\[.*$/, '').trim());
}).first();
if (!$heading.length) {
return;
}
var $list = $heading.nextAll('ul').first();
if (!$list.length) {
$list = $heading.parent().nextAll('ul').first();
}
if (!$list.length || $list.data('rbCampManualManage')) {
return;
}
$list.data('rbCampManualManage', 1);
 
var groups = mw.config.get('wgUserGroups') || [];
var canDeleteFile = groups.indexOf('sysop') !== -1;
var api = new mw.Api();
 
$list.children('li').each(function () {
var $li = $(this);
if ($li.find('.camp-manual-lib-actions').length) {
return;
}
var $a = $li.find('a.internal, a[href*="/images/"], a[href*="File:"], a[href*="Media:"]').first();
if (!$a.length) {
$a = $li.find('a[href]').first();
}
var filename = filenameFromHref($a.attr('href'), $a.attr('title'));
if (!filename) {
return;
}
var $actions = $('<span>', { 'class': 'camp-manual-lib-actions' });
var $remove = $('<button>', {
type: 'button',
'class': 'camp-manual-lib-remove',
text: 'Remove'
}).attr('title', 'Remove from this list (file stays on the wiki)');
$actions.append($remove);
if (canDeleteFile) {
$actions.append($('<button>', {
type: 'button',
'class': 'camp-manual-lib-delete',
text: 'Delete file'
}).attr('title', 'Delete the file from the wiki (administrators only)'));
}
$li.append($actions);
 
$remove.on('click', function () {
if (!window.confirm('Remove “' + filename.replace(/_/g, ' ') + '” from the Library list?\n(The file will remain on the wiki.)')) {
return;
}
$actions.find('button').prop('disabled', true);
removeFromLibrary(api, filename).done(function () {
window.location.reload();
}).fail(function (code, data) {
window.alert(failMessage(code, data));
$actions.find('button').prop('disabled', false);
});
});
 
$actions.on('click', '.camp-manual-lib-delete', function () {
if (!window.confirm('Permanently delete file “' + filename.replace(/_/g, ' ') + '” from the wiki?')) {
return;
}
$actions.find('button').prop('disabled', true);
deleteLibraryFile(api, filename).done(function () {
window.location.reload();
}).fail(function (code, data) {
window.alert(failMessage(code, data));
$actions.find('button').prop('disabled', false);
});
});
});
}
 
enhanceLibraryManage();
 
$form.on('submit', function (evt) {
evt.preventDefault();
var file = $file[0].files[0];
if (!file) {
setStatus('Choose a file first.', true);
return;
}
if (!DOC_EXT.test(file.name)) {
setStatus('File type not allowed (PDF, Office docs, images, TIFF).', true);
return;
}
var maxBytes = 120 * 1024 * 1024;
if (file.size > maxBytes) {
setStatus('File is too large (max about 120 MB). Split the PDF or compress it, then try again.', true);
return;
}
var displayTitle = $.trim($form.find('.camp-manual-title').val() || '');
var note = $.trim($form.find('.camp-manual-note').val() || '');
$submit.prop('disabled', true);
resetProgress();
setProgress(0, file.size);
setStatus('Uploading…');
 
var api = new mw.Api({ ajax: { timeout: 0 } });
var fileText = 'Camp Ritchie / MITC training material.';
var uploadedName = null;
var uploadParams = {
filename: file.name,
comment: 'Uploaded to Training materials library',
text: fileText,
ignorewarnings: true
};
 
function doUpload() {
/* Multipart + XHR progress (api.upload alone often has no progress events). */
return api.postWithToken('csrf', $.extend({
action: 'upload',
file: file,
format: 'json'
}, uploadParams), {
contentType: 'multipart/form-data',
timeout: 0,
xhr: function () {
var xhr = $.ajaxSettings.xhr();
if (xhr.upload) {
xhr.upload.addEventListener('progress', function (ev) {
if (ev.lengthComputable) {
setProgress(ev.loaded, ev.total);
}
});
}
return xhr;
}
});
}
 
doUpload().then(function (data) {
uploadedName = data.upload && data.upload.filename;
if (!uploadedName) {
throw new Error((data.error && data.error.info) || 'Upload failed');
}
setProgress(file.size, file.size);
$progress.addClass('is-indeterminate');
$progressLabel.text('Finishing…');
setStatus('Adding to Library list…');
return appendToLibrary(api, uploadedName, displayTitle, note);
}).done(function () {
$progress.removeClass('is-indeterminate');
$progressLabel.text('100% · done');
setStatus('Done. Reloading…');
window.location.reload();
}).fail(function (code, data) {
var err = failMessage(code, data);
if (uploadedName) {
err = 'File uploaded as “' + uploadedName + '”, but the Library list was not updated: ' + err +
' Refresh this page; if it is missing from the list, say so and we can add it.';
}
setStatus(String(err), true);
resetProgress();
$submit.prop('disabled', false);
});
});
});


(function rbBooksFilters() {
(function rbBooksFilters() {
Line 3,243: Line 3,885:
}());
}());


 
/* Related-media wrap removed: story/interview cues already sit under the lead
(function rbRelatedMediaWrap() {
* with their own labels (Full story, etc.). Wrapping them added a stray
var $out = $('.mw-parser-output').first();
* Related media heading near the top of the article. */
if (!$out.length) return;
var $cues = $out.find('.soldier-story-cue, .soldier-media-cue');
if ($cues.length < 1) return;
if ($out.find('.soldier-related-media').length) return;
var $wrap = $('<div class="soldier-related-media"><div class="soldier-related-media-title">Related media</div></div>');
$cues.first().before($wrap);
$cues.appendTo($wrap);
}());