add incremental search

* support modals in scrollElementIntoView and incremental search
This commit is contained in:
tophf 2022-01-25 22:52:37 +03:00
parent 79fcb5705a
commit 30eb6ed4f1
6 changed files with 46 additions and 22 deletions

View File

@ -2,6 +2,10 @@
height: 100%; height: 100%;
max-width: 80vw; max-width: 80vw;
} }
.injection-order #incremental-search {
transform: scaleY(.55);
transform-origin: top;
}
.injection-order #message-box-contents, .injection-order #message-box-contents,
.injection-order section { .injection-order section {
padding: 0; padding: 0;
@ -19,7 +23,7 @@
box-sizing: border-box; box-sizing: border-box;
} }
.injection-order ol { .injection-order ol {
padding: 1px 0; /* 1px for keyboard-focused element's outline */ padding: 0;
margin: 0; margin: 0;
font-size: 14px; font-size: 14px;
overflow-y: auto; overflow-y: auto;
@ -38,6 +42,8 @@
.injection-order-entry { .injection-order-entry {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
position: relative; /* for incremental-search */
padding: 1px 1px 1px 1rem; /* keyboard focus outline */
color: #000; color: #000;
transition: transform .25s ease-in-out; transition: transform .25s ease-in-out;
z-index: 1; z-index: 1;
@ -48,7 +54,7 @@
cursor: move; cursor: move;
} }
.injection-order-entry a[href] { .injection-order-entry a[href] {
padding: .4em 0 .4em 1rem; padding: .4em 0;
cursor: inherit; cursor: inherit;
} }
.injection-order-entry.enabled a[href] { .injection-order-entry.enabled a[href] {

View File

@ -36,7 +36,9 @@ async function InjectionOrder(show = true) {
entry.classList.toggle('enabled', style.enabled); entry.classList.toggle('enabled', style.enabled);
parts.name.href = '/edit.html?id=' + style.id; parts.name.href = '/edit.html?id=' + style.id;
parts.name.textContent = style.name; parts.name.textContent = style.name;
return entry.cloneNode(true); return Object.assign(entry.cloneNode(true), {
styleNameLowerCase: style.name.toLocaleLowerCase(),
});
} }
function makeList([type, styles]) { function makeList([type, styles]) {

View File

@ -280,13 +280,14 @@ function onDOMready() {
function scrollElementIntoView(element, {invalidMarginRatio = 0} = {}) { function scrollElementIntoView(element, {invalidMarginRatio = 0} = {}) {
// align to the top/bottom of the visible area if wasn't visible // align to the top/bottom of the visible area if wasn't visible
if (!element.parentNode) return; const parent = element.parentNode;
if (!parent) return;
const {top, height} = element.getBoundingClientRect(); const {top, height} = element.getBoundingClientRect();
const {top: parentTop, bottom: parentBottom} = element.parentNode.getBoundingClientRect(); const {top: parentTop, bottom: parentBottom} = parent.getBoundingClientRect();
const windowHeight = window.innerHeight; const windowHeight = window.innerHeight;
if (top < Math.max(parentTop, windowHeight * invalidMarginRatio) || if (top < Math.max(parentTop, windowHeight * invalidMarginRatio) ||
top > Math.min(parentBottom, windowHeight) - height - windowHeight * invalidMarginRatio) { top > Math.min(parentBottom, windowHeight) - height - windowHeight * invalidMarginRatio) {
window.scrollBy(0, top - windowHeight / 2 + height); parent.scrollBy(0, top - windowHeight / 2 + height);
} }
} }

View File

@ -0,0 +1,12 @@
#incremental-search {
position: absolute;
color: transparent;
border: 1px solid hsla(180, 100%, 50%, .25);
margin: -1px -2px;
overflow: hidden;
resize: none;
background-color: hsla(180, 100%, 50%, .1);
box-sizing: content-box;
pointer-events: none;
z-index: 2147483647,
}

View File

@ -1,6 +1,7 @@
/* global debounce */// toolbox.js /* global debounce */// toolbox.js
/* global installed */// manage.js /* global installed */// manage.js
/* global /* global
$$
$ $
$create $create
$isTextInput $isTextInput
@ -14,39 +15,35 @@
let prevTime = performance.now(); let prevTime = performance.now();
let focusedName = ''; let focusedName = '';
const input = $create('textarea', { const input = $create('textarea', {
id: 'incremental-search',
spellcheck: false, spellcheck: false,
attributes: {tabindex: -1}, attributes: {tabindex: -1},
oninput: incrementalSearch, oninput: incrementalSearch,
}); });
replaceInlineStyle({ replaceInlineStyle({
opacity: '0', opacity: '0',
position: 'absolute',
color: 'transparent',
border: '1px solid hsla(180, 100%, 100%, .5)',
margin: '-1px -2px',
overflow: 'hidden',
resize: 'none',
'background-color': 'hsla(180, 100%, 100%, .2)',
'box-sizing': 'content-box',
'pointer-events': 'none',
}); });
document.body.appendChild(input); document.body.appendChild(input);
window.on('keydown', maybeRefocus, true); window.on('keydown', maybeRefocus, true);
function incrementalSearch({key}, immediately) { function incrementalSearch(event, immediately) {
const {key} = event;
if (!immediately) { if (!immediately) {
debounce(incrementalSearch, 100, {}, true); debounce(incrementalSearch, 100, {}, true);
return; return;
} }
const direction = key === 'ArrowUp' ? -1 : key === 'ArrowDown' ? 1 : 0; const direction = key === 'ArrowUp' ? -1 : key === 'ArrowDown' ? 1 : 0;
const text = input.value.toLocaleLowerCase(); const text = input.value.toLocaleLowerCase();
if (direction) {
event.preventDefault();
}
if (!text.trim() || !direction && (text === prevText || focusedName.startsWith(text))) { if (!text.trim() || !direction && (text === prevText || focusedName.startsWith(text))) {
prevText = text; prevText = text;
return; return;
} }
let textAtPos = 1e6; let textAtPos = 1e6;
let rotated; let rotated;
const entries = [...installed.children]; const entries = $('#message-box') ? $$('.injection-order-entry') : [...installed.children];
const focusedIndex = entries.indexOf(focusedEntry); const focusedIndex = entries.indexOf(focusedEntry);
if (focusedIndex > 0) { if (focusedIndex > 0) {
if (direction > 0) { if (direction > 0) {
@ -74,7 +71,7 @@
} }
if (found && found !== focusedEntry) { if (found && found !== focusedEntry) {
focusedEntry = found; focusedEntry = found;
focusedLink = $('.style-name-link', found); focusedLink = $('a', found);
focusedName = found.styleNameLowerCase; focusedName = found.styleNameLowerCase;
scrollElementIntoView(found, {invalidMarginRatio: .25}); scrollElementIntoView(found, {invalidMarginRatio: .25});
animateElement(found, 'highlight-quick'); animateElement(found, 'highlight-quick');
@ -84,12 +81,17 @@
opacity: '1', opacity: '1',
}); });
focusedLink.prepend(input); focusedLink.prepend(input);
input.focus();
return true; return true;
} }
} }
function maybeRefocus(event) { function maybeRefocus(event) {
if (event.altKey || event.metaKey || $('#message-box')) { if (event.altKey || event.metaKey) {
return;
}
const modal = $('#message-box');
if (modal && !modal.classList.contains('injection-order')) {
return; return;
} }
const inTextInput = $isTextInput(event.target); const inTextInput = $isTextInput(event.target);
@ -99,7 +101,7 @@
(code === 'Slash' || key === '/') && !ctrl && !inTextInput) { (code === 'Slash' || key === '/') && !ctrl && !inTextInput) {
// focus search field on "/" or Ctrl-F key // focus search field on "/" or Ctrl-F key
event.preventDefault(); event.preventDefault();
$('#search').focus(); if (!modal) $('#search').focus();
return; return;
} }
if (ctrl || inTextInput && event.target !== input) { if (ctrl || inTextInput && event.target !== input) {

View File

@ -92,11 +92,12 @@ newUI.renderClass();
showStyles(styles, ids); showStyles(styles, ids);
require([ window.on('load', () => require([
'/manage/import-export', '/manage/import-export',
'/manage/incremental-search.css',
'/manage/incremental-search', '/manage/incremental-search',
'/manage/updater-ui', '/manage/updater-ui',
]); ]), {once: true});
})(); })();
msg.onExtension(onRuntimeMessage); msg.onExtension(onRuntimeMessage);