stackchain-dashboard/frontend/today-work.js
timmy cc38b3d0f6
All checks were successful
CI / lint (pull_request) Successful in 30s
CI / build-frontend (pull_request) Successful in 5s
feat: add ordered mobile Today queue (#279)
2026-08-08 07:58:49 +00:00

92 lines
2.9 KiB
JavaScript

function createTodayWork({ storage, getLogin, limit = 5 }) {
const prefix = 'stackchain.today-work.v1.';
function identity(item) {
if (!item) return '';
const kind = item.is_review ? 'review' : (item.kind || 'work');
const number = Number.isInteger(item.number) ? item.number : '';
const notification = Number.isInteger(item.notification_id) ? item.notification_id : '';
return [kind, item.repository || '', number, notification].join(':');
}
function storageKey() {
const login = String(getLogin?.() || '').trim().toLowerCase();
return login ? prefix + encodeURIComponent(login) : '';
}
function read() {
const key = storageKey();
if (!key || !storage) return [];
try {
const value = JSON.parse(storage.getItem(key) || '[]');
return Array.isArray(value) ? value.filter(id => typeof id === 'string' && id) : [];
} catch (_error) {
return [];
}
}
function write(ids) {
const key = storageKey();
if (!key || !storage) return false;
try {
if (ids.length) storage.setItem(key, JSON.stringify(ids));
else storage.removeItem(key);
return true;
} catch (_error) {
return false;
}
}
function add(item) {
if (!storageKey()) return 'unavailable';
const id = identity(item);
if (!id) return 'unavailable';
const ids = read();
if (ids.includes(id)) return 'exists';
if (ids.length >= limit) return 'full';
ids.push(id);
return write(ids) ? 'added' : 'unavailable';
}
function remove(item) {
const id = identity(item);
const ids = read();
const next = ids.filter(candidate => candidate !== id);
return next.length !== ids.length && write(next);
}
function move(item, direction) {
const ids = read();
const index = ids.indexOf(identity(item));
const target = direction === 'up' ? index - 1 : direction === 'down' ? index + 1 : -1;
if (index < 0 || target < 0 || target >= ids.length) return false;
[ids[index], ids[target]] = [ids[target], ids[index]];
return write(ids);
}
function reconcile(items, { pruneMissing = false } = {}) {
const available = new Map((items || []).map(item => [identity(item), item]));
const ids = read();
const retained = pruneMissing ? ids.filter(id => available.has(id)) : ids;
if (retained.length !== ids.length) write(retained);
return retained.flatMap(id => available.has(id) ? [available.get(id)] : []);
}
function contains(item) {
return read().includes(identity(item));
}
function position(item) {
const ids = read();
const index = ids.indexOf(identity(item));
return {
can_up: index > 0,
can_down: index >= 0 && index < ids.length - 1,
};
}
return { identity, add, remove, move, reconcile, contains, position, limit };
}
if (typeof module !== 'undefined' && module.exports) module.exports = createTodayWork;