103 lines
4.0 KiB
JavaScript
103 lines
4.0 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 scope = { kind:'all', state:'all' };
|
|
let state = { status: 'idle', query: '', items: [], more: false, next: 1, scope };
|
|
|
|
function publish(next) {
|
|
state = next;
|
|
onState(next);
|
|
return state;
|
|
}
|
|
|
|
async function requestPage(query, page, current, append) {
|
|
const requestController = new AbortController();
|
|
activeController = requestController;
|
|
try {
|
|
const requestScope = { ...scope };
|
|
const result = await search(query, requestController.signal, page, requestScope);
|
|
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) return publish({
|
|
status: 'ready', query, items, partial,
|
|
more: !!result.has_more,
|
|
next: result.next_page,
|
|
scope:requestScope,
|
|
});
|
|
} catch (error) {
|
|
if (error && error.name === 'AbortError') return;
|
|
if (current === generation) return publish(append
|
|
? { ...state, status: 'ready' }
|
|
: { status: 'error', query, items: [], error, scope:{ ...scope } });
|
|
} 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, scope:{ ...scope } });
|
|
return;
|
|
}
|
|
publish({ status: 'loading', query, items: [], more: false, next: 1, scope:{ ...scope } });
|
|
timer = setTimeout(() => requestPage(query, 1, current, false), delay);
|
|
},
|
|
setScope(value) {
|
|
const repository = typeof value?.repository === 'string' &&
|
|
/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(value.repository.trim())
|
|
? value.repository.trim() : '';
|
|
const next = {
|
|
kind:['all', 'issue', 'pull'].includes(value?.kind) ? value.kind : 'all',
|
|
state:['all', 'open', 'closed'].includes(value?.state) ? value.state : 'all',
|
|
...(repository ? { repository } : {}),
|
|
};
|
|
if (next.kind === scope.kind && next.state === scope.state &&
|
|
(next.repository || '') === (scope.repository || '')) return;
|
|
const query = state.query;
|
|
scope = next;
|
|
this.setQuery(query);
|
|
},
|
|
loadMore() {
|
|
if (state.status !== 'ready' || !state.more || activeController) return Promise.resolve(state);
|
|
const current = generation;
|
|
const page = state.next;
|
|
return requestPage(state.query, page, current, true);
|
|
},
|
|
};
|
|
};
|
|
|
|
return filterCommands;
|
|
});
|