stackchain-dashboard/frontend/mobile-queue-priority.js
timmy dbabd8640b
All checks were successful
CI / lint (pull_request) Successful in 3m53s
CI / build-release (pull_request) Successful in 7s
CI / browser-journey (pull_request) Successful in 7m39s
CI / release-candidate (pull_request) Has been skipped
feat: personalize adaptive mobile queue priority (Closes #1460)
2026-08-27 07:09:06 +00:00

124 lines
4.4 KiB
JavaScript

(function (root, factory) {
if (typeof module === 'object' && module.exports) module.exports = factory;
else root.createMobileQueuePriority = factory;
})(typeof self !== 'undefined' ? self : this, function createMobileQueuePriority(options = {}) {
const DEFAULT_ORDER = [
'attention', 'today', 'update', 'agenda', 'following', 'authored', 'filed', 'later', 'draft',
];
const storage = options.storage;
const getLogin = options.getLogin || (() => '');
const prefix = 'stackchain-mobile-queue-priority-v1:';
const labels = options.labels || {};
const documentRef = options.document || (typeof document !== 'undefined' ? document : null);
function key() {
const login = String(getLogin() || '').trim().toLowerCase();
return login ? prefix + encodeURIComponent(login) : '';
}
function valid(order) {
return Array.isArray(order) && order.length === DEFAULT_ORDER.length &&
new Set(order).size === DEFAULT_ORDER.length &&
order.every((name, index) => DEFAULT_ORDER.includes(name) && typeof name === 'string');
}
function getOrder() {
const accountKey = key();
if (!accountKey || !storage) return DEFAULT_ORDER.slice();
try {
const saved = JSON.parse(storage.getItem(accountKey) || 'null');
return valid(saved) ? saved.slice() : DEFAULT_ORDER.slice();
} catch (_error) {
return DEFAULT_ORDER.slice();
}
}
function save(order) {
const accountKey = key();
if (!accountKey || !storage || !valid(order)) return false;
try {
storage.setItem(accountKey, JSON.stringify(order));
options.onChange?.(order.slice());
return true;
} catch (_error) {
return false;
}
}
function move(name, delta) {
const order = getOrder();
const index = order.indexOf(name);
const next = index + (Number(delta) < 0 ? -1 : 1);
if (index < 0 || next < 0 || next >= order.length) return order;
[order[index], order[next]] = [order[next], order[index]];
save(order);
return order.slice();
}
function reset() {
const accountKey = key();
if (accountKey && storage) {
try { storage.removeItem(accountKey); } catch (_error) {}
}
const order = DEFAULT_ORDER.slice();
options.onChange?.(order.slice());
return order;
}
function displayName(name) {
return labels[name] || name.charAt(0).toUpperCase() + name.slice(1);
}
function render() {
if (!options.list || !documentRef) return getOrder();
const order = getOrder();
const signedIn = Boolean(key());
const rows = order.map((name, index) => {
const row = documentRef.createElement('div');
row.setAttribute('data-queue-priority', name);
row.setAttribute('class', 'mobile-queue-priority-row');
const label = documentRef.createElement('span');
label.textContent = displayName(name);
const controls = documentRef.createElement('span');
controls.setAttribute('class', 'mobile-queue-priority-controls');
const earlier = documentRef.createElement('button');
earlier.textContent = 'Earlier';
earlier.setAttribute('type', 'button');
earlier.setAttribute('aria-label', 'Move ' + displayName(name) + ' earlier');
earlier.disabled = !signedIn || index === 0;
earlier.addEventListener('click', () => {
move(name, -1);
if (options.status) options.status.textContent = displayName(name) + ' moved earlier.';
render();
});
const later = documentRef.createElement('button');
later.textContent = 'Later';
later.setAttribute('type', 'button');
later.setAttribute('aria-label', 'Move ' + displayName(name) + ' later');
later.disabled = !signedIn || index === order.length - 1;
later.addEventListener('click', () => {
move(name, 1);
if (options.status) options.status.textContent = displayName(name) + ' moved later.';
render();
});
controls.append(earlier, later);
row.append(label, controls);
return row;
});
options.list.replaceChildren(...rows);
if (options.resetButton) options.resetButton.disabled = !signedIn;
return order;
}
function start() {
options.resetButton?.addEventListener('click', () => {
reset();
if (options.status) options.status.textContent = 'Routine order reset.';
render();
});
return render();
}
return {getOrder, move, reset, render, start, defaultOrder: () => DEFAULT_ORDER.slice()};
});