MediaWiki:Common.js: Difference between revisions
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>'); | ||
$ | var $input = $('<input type="search" name="search" id="home-search" class="home-find-input" autocomplete="off" aria-autocomplete="list" aria-controls="home-find-suggest" />') | ||
.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($ | $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; | |||
}); | |||
}()); | }()); | ||