stackchain-dashboard/frontend/commands.js
timmy 7b736bc4c6
All checks were successful
CI / lint (pull_request) Successful in 1m34s
CI / build-release (pull_request) Successful in 5s
CI / release-candidate (pull_request) Has been skipped
feat: paginate balanced mobile search results (Closes #783)
2026-08-14 00:04:06 +00:00

84 lines
3.1 KiB
JavaScript

(function (root, factory) {
const filterCommands = factory();
if (typeof module === 'object' && module.exports) module.exports = filterCommands;
if (root) root.filterCommands = filterCommands;
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
function filterCommands(commands, filter) {
const term = String(filter || '').toLowerCase();
return commands.filter(command => command.name.toLowerCase().includes(term));
}
filterCommands.nextSelection = function nextSelection(current, key, count) {
if (count < 1) return -1;
if (key === 'ArrowDown') return (current + 1 + count) % count;
if (key === 'ArrowUp') return (current - 1 + count) % count;
return current;
};
filterCommands.createGlobalSearchController = function createGlobalSearchController(options) {
const search = options.search;
const onState = options.onState;
const delay = options.delay === undefined ? 250 : options.delay;
let timer = null;
let generation = 0;
let activeController = null;
let state = { status: 'idle', query: '', items: [], more: false, next: 1 };
function publish(next) {
state = next;
onState(next);
}
async function requestPage(query, page, current, append) {
const requestController = new AbortController();
activeController = requestController;
try {
const result = await search(query, requestController.signal, page);
const partial = result.partial === true;
const incoming = result.items || result;
const combined = append ? state.items.concat(incoming) : incoming;
const items = [...new Map((combined || []).map(item =>
[`${item.kind}:${item.repository}:${item.number}`, item]
)).values()];
if (current === generation) publish({
status: 'ready', query, items, partial,
more: !!result.has_more,
next: result.next_page,
});
} catch (error) {
if (error && error.name === 'AbortError') return;
if (current === generation) publish(append
? { ...state, status: 'ready' }
: { status: 'error', query, items: [], error });
} finally {
if (activeController === requestController) activeController = null;
}
}
return {
setQuery(value) {
const query = String(value || '').trim();
generation += 1;
const current = generation;
if (timer !== null) clearTimeout(timer);
if (activeController !== null) activeController.abort();
activeController = null;
if (query.length < 2) {
publish({ status: 'idle', query, items: [], more: false, next: 1 });
return;
}
publish({ status: 'loading', query, items: [], more: false, next: 1 });
timer = setTimeout(() => requestPage(query, 1, current, false), delay);
},
loadMore() {
if (state.status !== 'ready' || !state.more || activeController) return;
const current = generation;
const page = state.next;
requestPage(state.query, page, current, true);
},
};
};
return filterCommands;
});