mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-17 12:22:21 +02:00
Squash Odysseus development history
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
import {
|
||||
SETTINGS_GROUPS,
|
||||
getSettingsPanel,
|
||||
searchSettingsPanels,
|
||||
} from './registry.js';
|
||||
|
||||
const _boundModals = new WeakSet();
|
||||
|
||||
function groupLabelFor(panel) {
|
||||
const group = SETTINGS_GROUPS.find(candidate => candidate.id === panel.group);
|
||||
return group?.label || '';
|
||||
}
|
||||
|
||||
function clearResults(resultsEl) {
|
||||
if (!resultsEl) return;
|
||||
resultsEl.replaceChildren();
|
||||
resultsEl.classList.add('hidden');
|
||||
}
|
||||
|
||||
function getResultButtons(resultsEl) {
|
||||
if (!resultsEl) return [];
|
||||
return Array.from(resultsEl.querySelectorAll('[data-settings-search-result]'));
|
||||
}
|
||||
|
||||
function setActiveResult(resultsEl, index) {
|
||||
const buttons = getResultButtons(resultsEl);
|
||||
if (!buttons.length) return -1;
|
||||
|
||||
let next = index;
|
||||
if (next < 0) next = buttons.length - 1;
|
||||
if (next >= buttons.length) next = 0;
|
||||
|
||||
buttons.forEach((button, buttonIndex) => {
|
||||
const active = buttonIndex === next;
|
||||
button.classList.toggle('active', active);
|
||||
button.setAttribute('aria-selected', active ? 'true' : 'false');
|
||||
});
|
||||
|
||||
if (typeof buttons[next].scrollIntoView === 'function') {
|
||||
buttons[next].scrollIntoView({ block: 'nearest' });
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
export function bindSettingsSearch(modalEl, options = {}) {
|
||||
if (!modalEl || _boundModals.has(modalEl)) return;
|
||||
|
||||
const input = modalEl.querySelector('#settings-nav-search');
|
||||
const resultsEl = modalEl.querySelector('#settings-nav-search-results');
|
||||
|
||||
if (!input || !resultsEl) return;
|
||||
_boundModals.add(modalEl);
|
||||
|
||||
const isAdmin = typeof options.isAdmin === 'function'
|
||||
? options.isAdmin
|
||||
: () => false;
|
||||
|
||||
const openPanel = typeof options.openPanel === 'function'
|
||||
? options.openPanel
|
||||
: () => {};
|
||||
|
||||
let activeIndex = -1;
|
||||
|
||||
function reset() {
|
||||
input.value = '';
|
||||
activeIndex = -1;
|
||||
clearResults(resultsEl);
|
||||
}
|
||||
|
||||
function activateResult(button) {
|
||||
const panelId = button?.dataset?.settingsSearchResult;
|
||||
if (!panelId || !getSettingsPanel(panelId)) return;
|
||||
|
||||
openPanel(panelId);
|
||||
reset();
|
||||
}
|
||||
|
||||
function render() {
|
||||
const query = input.value.trim();
|
||||
activeIndex = -1;
|
||||
resultsEl.replaceChildren();
|
||||
|
||||
if (!query) {
|
||||
resultsEl.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
const matches = searchSettingsPanels(query, {
|
||||
isAdmin: isAdmin(),
|
||||
});
|
||||
|
||||
if (!matches.length) {
|
||||
const empty = document.createElement('div');
|
||||
empty.className = 'settings-search-empty';
|
||||
empty.textContent = 'No settings found';
|
||||
resultsEl.appendChild(empty);
|
||||
resultsEl.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const panel of matches) {
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.className = 'settings-search-result';
|
||||
button.setAttribute('data-settings-search-result', panel.id);
|
||||
button.setAttribute('role', 'option');
|
||||
button.setAttribute('aria-selected', 'false');
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.className = 'settings-search-result-label';
|
||||
label.textContent = panel.label;
|
||||
|
||||
const group = document.createElement('span');
|
||||
group.className = 'settings-search-result-group';
|
||||
group.textContent = groupLabelFor(panel);
|
||||
|
||||
button.append(label, group);
|
||||
button.addEventListener('click', () => activateResult(button));
|
||||
resultsEl.appendChild(button);
|
||||
}
|
||||
|
||||
resultsEl.classList.remove('hidden');
|
||||
}
|
||||
|
||||
input.addEventListener('input', render);
|
||||
|
||||
input.addEventListener('focus', () => {
|
||||
if (input.value.trim()) render();
|
||||
});
|
||||
|
||||
input.addEventListener('keydown', event => {
|
||||
const buttons = getResultButtons(resultsEl);
|
||||
|
||||
if (event.key === 'Escape') {
|
||||
if (!input.value && resultsEl.classList.contains('hidden')) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!buttons.length) return;
|
||||
|
||||
if (event.key === 'ArrowDown') {
|
||||
event.preventDefault();
|
||||
activeIndex = setActiveResult(resultsEl, activeIndex + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowUp') {
|
||||
event.preventDefault();
|
||||
activeIndex = setActiveResult(resultsEl, activeIndex - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === 'Enter') {
|
||||
const target = buttons[activeIndex >= 0 ? activeIndex : 0];
|
||||
if (!target) return;
|
||||
event.preventDefault();
|
||||
activateResult(target);
|
||||
}
|
||||
});
|
||||
|
||||
modalEl.addEventListener('mousedown', event => {
|
||||
if (event.target === input || resultsEl.contains(event.target)) return;
|
||||
clearResults(resultsEl);
|
||||
activeIndex = -1;
|
||||
});
|
||||
|
||||
return { reset, render };
|
||||
}
|
||||
Reference in New Issue
Block a user