manage: incremental search on typing + up/down arrows
This commit is contained in:
parent
b762f48135
commit
362d944428
|
@ -92,8 +92,11 @@ function onDOMready() {
|
|||
function scrollElementIntoView(element) {
|
||||
// align to the top/bottom of the visible area if wasn't visible
|
||||
const bounds = element.getBoundingClientRect();
|
||||
if (bounds.top < 0 || bounds.top > innerHeight - bounds.height) {
|
||||
element.scrollIntoView(bounds.top < 0);
|
||||
const boundsContainer = element.parentNode.getBoundingClientRect();
|
||||
const windowHeight = window.innerHeight;
|
||||
if (bounds.top < Math.max(boundsContainer.top, windowHeight / 4) ||
|
||||
bounds.top > Math.min(boundsContainer.bottom, windowHeight) - bounds.height - windowHeight / 4) {
|
||||
window.scrollBy(0, bounds.top - windowHeight / 2 + bounds.height);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -273,6 +273,7 @@
|
|||
|
||||
<script src="manage/import-export.js"></script>
|
||||
<script src="msgbox/msgbox.js"></script>
|
||||
<script src="manage/incremental-search.js" async></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
132
manage/incremental-search.js
Normal file
132
manage/incremental-search.js
Normal file
|
@ -0,0 +1,132 @@
|
|||
/* global installed */
|
||||
'use strict';
|
||||
|
||||
onDOMready().then(() => {
|
||||
let prevText, focusedLink, focusedEntry;
|
||||
let prevTime = performance.now();
|
||||
let focusedName = '';
|
||||
const input = $element({
|
||||
tag: 'textarea',
|
||||
spellcheck: false,
|
||||
oninput: incrementalSearch,
|
||||
});
|
||||
replaceInlineStyle({
|
||||
position: 'absolute',
|
||||
color: 'transparent',
|
||||
border: '1px solid hsla(180, 100%, 100%, .5)',
|
||||
top: '-1000px',
|
||||
overflow: 'hidden',
|
||||
resize: 'none',
|
||||
'background-color': 'hsla(180, 100%, 100%, .2)',
|
||||
'pointer-events': 'none',
|
||||
});
|
||||
document.body.appendChild(input);
|
||||
window.addEventListener('keydown', maybeRefocus, true);
|
||||
|
||||
function incrementalSearch({which}, immediately) {
|
||||
if (!immediately) {
|
||||
debounce(incrementalSearch, 100, {}, true);
|
||||
return;
|
||||
}
|
||||
const direction = which === 38 ? -1 : which === 40 ? 1 : 0;
|
||||
const text = input.value.toLocaleLowerCase();
|
||||
if (!text.trim() || !direction && (text === prevText || focusedName.startsWith(text))) {
|
||||
prevText = text;
|
||||
return;
|
||||
}
|
||||
let textAtPos = 1e6;
|
||||
let rotated;
|
||||
const entries = [...installed.children];
|
||||
const focusedIndex = entries.indexOf(focusedEntry);
|
||||
if (focusedIndex > 0) {
|
||||
if (direction > 0) {
|
||||
rotated = entries.slice(focusedIndex + 1).concat(entries.slice(0, focusedIndex + 1));
|
||||
} else if (direction < 0) {
|
||||
rotated = entries.slice(0, focusedIndex).reverse().concat(entries.slice(focusedIndex).reverse());
|
||||
}
|
||||
}
|
||||
let found;
|
||||
for (const entry of rotated || entries) {
|
||||
const name = entry.styleNameLowerCase;
|
||||
const pos = name.indexOf(text);
|
||||
if (pos === 0) {
|
||||
found = entry;
|
||||
break;
|
||||
} else if (pos > 0 && (pos < textAtPos || direction)) {
|
||||
found = entry;
|
||||
textAtPos = pos;
|
||||
if (direction) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found && found !== focusedEntry) {
|
||||
focusedEntry = found;
|
||||
focusedLink = $('.style-name-link', found);
|
||||
focusedName = found.styleNameLowerCase;
|
||||
scrollElementIntoView(found);
|
||||
animateElement(found, {className: 'highlight-quick'});
|
||||
resizeTo(focusedLink);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function maybeRefocus(event) {
|
||||
if (event.altKey || event.ctrlKey || event.metaKey ||
|
||||
event.target.matches('[type="text"], [type="search"]')) {
|
||||
return;
|
||||
}
|
||||
const k = event.which;
|
||||
// focus search field on "/" key
|
||||
if (k === 191 && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
$('#search').focus();
|
||||
return;
|
||||
}
|
||||
const time = performance.now();
|
||||
if (
|
||||
// 0-9
|
||||
k >= 48 && k <= 57 ||
|
||||
// a-z
|
||||
k >= 65 && k <= 90 ||
|
||||
// numpad keys
|
||||
k >= 96 && k <= 111 ||
|
||||
// marks
|
||||
k >= 186
|
||||
) {
|
||||
input.focus();
|
||||
if (time - prevTime > 1000) {
|
||||
input.value = '';
|
||||
}
|
||||
prevTime = time;
|
||||
} else
|
||||
if (k === 13 && focusedLink) {
|
||||
focusedLink.dispatchEvent(new MouseEvent('click', {bubbles: true}));
|
||||
} else
|
||||
if ((k === 38 || k === 40) && !event.shiftKey &&
|
||||
time - prevTime < 5000 && incrementalSearch(event, true)) {
|
||||
prevTime = time;
|
||||
} else
|
||||
if (event.target === input) {
|
||||
(focusedLink || document.body).focus();
|
||||
input.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function resizeTo(el) {
|
||||
const bounds = el.getBoundingClientRect();
|
||||
const base = document.scrollingElement;
|
||||
replaceInlineStyle({
|
||||
left: bounds.left - 2 + base.scrollLeft + 'px',
|
||||
top: bounds.top - 1 + base.scrollTop + 'px',
|
||||
width: bounds.width + 4 + 'px',
|
||||
height: bounds.height + 2 + 'px',
|
||||
});
|
||||
}
|
||||
|
||||
function replaceInlineStyle(css) {
|
||||
for (const prop in css) {
|
||||
input.style.setProperty(prop, css[prop], 'important');
|
||||
}
|
||||
}
|
||||
});
|
|
@ -584,6 +584,9 @@ input[id^="manage.newUI"] {
|
|||
.highlight {
|
||||
animation: highlight 10s cubic-bezier(0,.82,.47,.98);
|
||||
}
|
||||
.highlight-quick {
|
||||
animation: highlight .5s;
|
||||
}
|
||||
|
||||
@keyframes highlight {
|
||||
from {
|
||||
|
|
|
@ -59,16 +59,6 @@ function initGlobalEvents() {
|
|||
$('#manage-shortcuts-button').onclick = () => openURL({url: URLS.configureCommands});
|
||||
$$('#header a[href^="http"]').forEach(a => (a.onclick = handleEvent.external));
|
||||
|
||||
// focus search field on / key
|
||||
document.onkeypress = event => {
|
||||
if ((event.keyCode || event.which) === 47
|
||||
&& !event.altKey && !event.shiftKey && !event.ctrlKey && !event.metaKey
|
||||
&& !event.target.matches('[type="text"], [type="search"]')) {
|
||||
event.preventDefault();
|
||||
$('#search').focus();
|
||||
}
|
||||
};
|
||||
|
||||
// remember scroll position on normal history navigation
|
||||
window.onbeforeunload = rememberScrollPosition;
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user