feat: add mobile attention queue (#244)
This commit is contained in:
1 parent
e0bc7271fa
commit
51ac802d77
7 files changed
+102
-13
No files matched your search
+15
-4
@@ -307,6 +307,7 @@ textarea { resize: vertical; min-height: 120px; }
|
||||
</div>
|
||||
<div class="work-filters" aria-label="Filter My Work">
|
||||
<button class="work-filter" data-work-filter="all" aria-pressed="true">All <span data-work-count="all">0</span></button>
|
||||
<button class="work-filter" data-work-filter="attention" aria-pressed="false">Attention <span data-work-count="attention">0</span></button>
|
||||
<button class="work-filter" data-work-filter="issue" aria-pressed="false">Issues <span data-work-count="issue">0</span></button>
|
||||
<button class="work-filter" data-work-filter="pull" aria-pressed="false">PRs <span data-work-count="pull">0</span></button>
|
||||
<button class="work-filter" data-work-filter="review" aria-pressed="false">Reviews <span data-work-count="review">0</span></button>
|
||||
@@ -725,7 +726,7 @@ textarea { resize: vertical; min-height: 120px; }
|
||||
</div>
|
||||
|
||||
<nav class="mobile-task-dock" id="mobile-task-dock" aria-label="Primary tasks">
|
||||
<button class="mobile-task-action" data-mobile-task="work" type="button" aria-current="page">Work</button>
|
||||
<button class="mobile-task-action" data-mobile-task="work" type="button" aria-current="page">Work <span class="mobile-task-count" id="mobile-attention-count" hidden>0</span></button>
|
||||
<button class="mobile-task-action" data-mobile-task="find" type="button">Find</button>
|
||||
<button class="mobile-task-action" data-mobile-task="new" type="button">New</button>
|
||||
<button class="mobile-task-action" data-mobile-task="search" type="button">Search</button>
|
||||
@@ -782,12 +783,20 @@ textarea { resize: vertical; min-height: 120px; }
|
||||
Array.from(document.querySelectorAll('[data-mobile-task]')).map(button => [button.dataset.mobileTask, button])
|
||||
);
|
||||
const mobileTaskOverlays = Array.from(document.querySelectorAll('[role="dialog"], #whiteboard-modal, #markdown-modal'));
|
||||
function openMobileWork() {
|
||||
const counts = countMyWork(lastMyWork);
|
||||
const filter = counts.attention ? 'attention' : 'all';
|
||||
qs('[data-work-filter="' + filter + '"]').click();
|
||||
qs('#my-work').scrollIntoView({block:'start'});
|
||||
qs('#my-work').focus();
|
||||
}
|
||||
const mobileTaskDock = createMobileTaskDock({
|
||||
nav: qs('#mobile-task-dock'),
|
||||
buttons: mobileTaskButtons,
|
||||
attentionBadge: qs('#mobile-attention-count'),
|
||||
overlays: mobileTaskOverlays,
|
||||
actions: {
|
||||
work: () => qs('#my-work').scrollIntoView({block:'start'}) || qs('#my-work').focus(),
|
||||
work: openMobileWork,
|
||||
find: () => qs('#find-work').click(),
|
||||
new: () => qs('#new-issue').click(),
|
||||
search: () => qs('#open-palette').click(),
|
||||
@@ -816,7 +825,7 @@ textarea { resize: vertical; min-height: 120px; }
|
||||
let selectedWorkMilestone = 'all';
|
||||
try {
|
||||
const savedFilter = sessionStorage.getItem(WORK_FILTER_KEY);
|
||||
if (['all', 'issue', 'pull', 'review', 'update', 'draft'].includes(savedFilter)) selectedWorkFilter = savedFilter;
|
||||
if (['all', 'attention', 'issue', 'pull', 'review', 'update', 'draft'].includes(savedFilter)) selectedWorkFilter = savedFilter;
|
||||
const savedMilestone = sessionStorage.getItem(WORK_MILESTONE_KEY);
|
||||
if (savedMilestone) selectedWorkMilestone = savedMilestone;
|
||||
} catch (e) {
|
||||
@@ -1285,6 +1294,7 @@ textarea { resize: vertical; min-height: 120px; }
|
||||
const element = qs('[data-work-count="' + filter + '"]');
|
||||
if (element) element.textContent = count;
|
||||
});
|
||||
mobileTaskDock.updateAttention(counts.attention);
|
||||
const milestoneSelect = qs('#work-milestone-filter');
|
||||
const lanes = milestoneLanes(lastMyWork);
|
||||
milestoneSelect.innerHTML = '<option value="all">All milestones</option>' +
|
||||
@@ -1308,6 +1318,7 @@ textarea { resize: vertical; min-height: 120px; }
|
||||
}
|
||||
|
||||
function activeWorkStreams() {
|
||||
if (selectedWorkFilter === 'attention') return ['issue', 'pull', 'review'];
|
||||
if (selectedWorkFilter === 'issue') return ['issue'];
|
||||
if (selectedWorkFilter === 'pull') return ['pull'];
|
||||
if (selectedWorkFilter === 'review') return ['review'];
|
||||
@@ -1450,7 +1461,7 @@ textarea { resize: vertical; min-height: 120px; }
|
||||
return '<article class="my-work-card"><a class="my-work-card-main update-trigger" href="' + escAttr(routeHref) + '" data-update-index="' + index + '">' + contents + '</a>' + markRead + '</article>';
|
||||
}).join('') : '<div class="muted">' + (incomplete ?
|
||||
'More work is available. Load the next page.' :
|
||||
'No ' + (selectedWorkFilter === 'review' ? 'reviews' : (selectedWorkFilter === 'update' ? 'unread updates' : (selectedWorkFilter === 'all' ? 'work' : selectedWorkFilter + ' items'))) + '.') + '</div>';
|
||||
'No ' + (selectedWorkFilter === 'attention' ? 'items need attention' : (selectedWorkFilter === 'review' ? 'reviews' : (selectedWorkFilter === 'update' ? 'unread updates' : (selectedWorkFilter === 'all' ? 'work' : selectedWorkFilter + ' items')))) + '.') + '</div>';
|
||||
document.querySelectorAll('[data-review-index]').forEach(button => {
|
||||
button.addEventListener('click', event => { event.preventDefault(); openRoutedWork(lastMyWork[Number(button.dataset.reviewIndex)], button); });
|
||||
});
|
||||
|
||||
@@ -34,5 +34,20 @@
|
||||
refreshVisibility();
|
||||
}
|
||||
|
||||
return {start, refreshVisibility};
|
||||
function updateAttention(count) {
|
||||
const total = Math.max(0, Number(count) || 0);
|
||||
const badge = options.attentionBadge;
|
||||
if (badge) {
|
||||
badge.textContent = String(total);
|
||||
badge.hidden = total === 0;
|
||||
}
|
||||
if (buttons.work) {
|
||||
buttons.work.setAttribute(
|
||||
'aria-label',
|
||||
total ? 'Work, ' + total + ' items need attention' : 'Work'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {start, refreshVisibility, updateAttention};
|
||||
});
|
||||
+3
-1
@@ -400,7 +400,8 @@ function createNotificationReplier({
|
||||
|
||||
function filterMyWork(items, selectedFilter, selectedMilestone = 'all') {
|
||||
let filtered = items;
|
||||
if (selectedFilter === 'review') filtered = items.filter((item) => item.is_review);
|
||||
if (selectedFilter === 'attention') filtered = items.filter((item) => item.has_update || item.is_review);
|
||||
else if (selectedFilter === 'review') filtered = items.filter((item) => item.is_review);
|
||||
else if (selectedFilter === 'update') filtered = items.filter((item) => item.has_update);
|
||||
else if (selectedFilter !== 'all') filtered = items.filter((item) => item.kind === selectedFilter);
|
||||
if (selectedMilestone === 'all') return filtered;
|
||||
@@ -559,6 +560,7 @@ function summarizeMyWork(items) {
|
||||
function countMyWork(items) {
|
||||
return {
|
||||
all: items.length,
|
||||
attention: items.filter((item) => item.has_update || item.is_review).length,
|
||||
issue: items.filter((item) => item.kind === 'issue').length,
|
||||
pull: items.filter((item) => item.kind === 'pull' && !item.is_review).length,
|
||||
review: items.filter((item) => item.is_review).length,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const CACHE = 'stackchain-dashboard-shell-v10';
|
||||
const CACHE = 'stackchain-dashboard-shell-v11';
|
||||
const OUTAGE_STATUSES = new Set([500, 502, 503, 504]);
|
||||
const BASE = new URL('./', self.location.href).pathname;
|
||||
const SHELL = [
|
||||
|
||||
Reference in new issue
Block a user