Place story cue and related rail on soldier pages
Add live suggestions to Main Page Find a soldier
Line 4: Line 4:
  * Move research-notes and sources-confidence cues to the bottom of soldier pages.
  * Move research-notes and sources-confidence cues to the bottom of soldier pages.
  * Split mashed CSV notes into separate bullets, then link MII/IPW team codes.
  * Split mashed CSV notes into separate bullets, then link MII/IPW team codes.
  * Build the Main Page find-a-soldier search form (raw HTML forms are stripped from wikitext).
  * Build the Main Page find-a-soldier search form (raw HTML forms are stripped from wikitext)
* with live title suggestions (same idea as the header search box).
  * Reorder Special:Search hits so title matches and site pages rank above research-notes noise.
  * Reorder Special:Search hits so title matches and site pages rank above research-notes noise.
  */
  */
Line 19: Line 20:
$form.append($('<input type="hidden" name="title" value="Special:Search" />'));
$form.append($('<input type="hidden" name="title" value="Special:Search" />'));
$form.append($('<label class="home-find-label" for="home-search"></label>').text('Name or ASN'));
$form.append($('<label class="home-find-label" for="home-search"></label>').text('Name or ASN'));
var $wrap = $('<div class="home-find-suggest-wrap"></div>');
var $row = $('<div class="home-find-row"></div>');
var $row = $('<div class="home-find-row"></div>');
$row.append(
var $input = $('<input type="search" name="search" id="home-search" class="home-find-input" autocomplete="off" aria-autocomplete="list" aria-controls="home-find-suggest" />')
$('<input type="search" name="search" id="home-search" class="home-find-input" autocomplete="off" />')
.attr('placeholder', 'e.g. Guy Stern or 37413105');
.attr('placeholder', 'e.g. Guy Stern or 37413105')
var $menu = $('<ul id="home-find-suggest" class="home-find-suggest" role="listbox" hidden></ul>').css({
);
position: 'absolute',
left: 0,
right: 0,
top: '100%',
zIndex: 50,
margin: '0.15em 0 0',
padding: '0.2em 0',
listStyle: 'none',
background: '#fff',
border: '1px solid #a2a9b1',
boxShadow: '0 2px 8px rgba(0,0,0,0.12)',
maxHeight: '16em',
overflowY: 'auto'
});
$wrap.css({ position: 'relative' });
$row.append($input);
$row.append($('<button type="submit" class="home-find-button"></button>').text('Search'));
$row.append($('<button type="submit" class="home-find-button"></button>').text('Search'));
$form.append($row);
$wrap.append($row).append($menu);
$form.append($wrap);
$slot.find('.home-find-title').after($form);
$slot.find('.home-find-title').after($form);
var timer = null;
var reqId = 0;
var active = -1;
var titles = [];
var soldierTitle = /\(\d{5,9}\)\s*$/;
function hideSuggest() {
$menu.attr('hidden', 'hidden').empty();
active = -1;
titles = [];
$input.attr('aria-expanded', 'false');
}
function goTitle(title) {
if (!title) {
return;
}
window.location.href = mw.util.getUrl(title);
}
function setActive(idx) {
var $items = $menu.children('li');
if (!$items.length) {
active = -1;
return;
}
if (idx < 0) {
idx = $items.length - 1;
}
if (idx >= $items.length) {
idx = 0;
}
active = idx;
$items.css('background', '').removeClass('home-find-suggest-active').attr('aria-selected', 'false');
$items.eq(active).css('background', '#eaf3ff').addClass('home-find-suggest-active').attr('aria-selected', 'true');
}
function render(list) {
titles = list || [];
$menu.empty();
if (!titles.length) {
hideSuggest();
return;
}
titles.forEach(function (title, i) {
var $li = $('<li role="option"></li>')
.attr('id', 'home-find-suggest-' + i)
.css({ margin: 0, padding: '0.4em 0.65em', cursor: 'pointer', fontSize: '95%', lineHeight: 1.35 })
.text(title)
.on('mouseenter', function () {
setActive(i);
})
.on('mousedown', function (e) {
e.preventDefault();
goTitle(title);
});
$menu.append($li);
});
$menu.removeAttr('hidden');
$input.attr('aria-expanded', 'true');
active = -1;
}
function rankTitles(query, hits) {
var q = String(query || '').trim().toLowerCase();
var scored = (hits || []).map(function (h) {
var t = h.title || '';
var low = t.toLowerCase();
var score = 0;
if (soldierTitle.test(t)) {
score += 50;
}
if (low === q) {
score += 100;
}
if (low.indexOf(q) === 0) {
score += 40;
}
if (low.indexOf(q) !== -1) {
score += 20;
}
if (q && low.indexOf('(' + q + ')') !== -1) {
score += 80;
}
return { title: t, score: score };
});
scored.sort(function (a, b) {
return b.score - a.score;
});
var seen = {};
var out = [];
scored.forEach(function (row) {
if (seen[row.title]) {
return;
}
seen[row.title] = 1;
out.push(row.title);
});
return out.slice(0, 8);
}
function fetchSuggest(query) {
var q = String(query || '').trim();
if (q.length < 2) {
hideSuggest();
return;
}
var myId = ++reqId;
new mw.Api().get({
action: 'query',
list: 'search',
srsearch: q,
srnamespace: 0,
srlimit: 12,
srwhat: 'title'
}).done(function (data) {
if (myId !== reqId) {
return;
}
if (String($input.val() || '').trim() !== q) {
return;
}
var hits = (((data || {}).query) || {}).search || [];
render(rankTitles(q, hits));
}).fail(function () {
if (myId === reqId) {
hideSuggest();
}
});
}
$input.on('input', function () {
window.clearTimeout(timer);
timer = window.setTimeout(function () {
fetchSuggest($input.val());
}, 180);
});
$input.on('keydown', function (e) {
var key = e.key;
if ($menu.attr('hidden') !== undefined && $menu.is('[hidden]')) {
if (key === 'ArrowDown' && String($input.val() || '').trim().length >= 2) {
fetchSuggest($input.val());
}
return;
}
if (key === 'ArrowDown') {
e.preventDefault();
setActive(active + 1);
} else if (key === 'ArrowUp') {
e.preventDefault();
setActive(active - 1);
} else if (key === 'Escape') {
hideSuggest();
} else if (key === 'Enter' && active >= 0 && titles[active]) {
e.preventDefault();
goTitle(titles[active]);
}
});
$input.on('blur', function () {
window.setTimeout(hideSuggest, 150);
});
$form.on('submit', function () {
if (active >= 0 && titles[active]) {
goTitle(titles[active]);
return false;
}
return true;
});
}());
}());