feat: resolve shared My Work routes
This commit is contained in:
1 parent
2fecccfbde
commit
b5545e380e
6 files changed
+328
-3
No files matched your search
@@ -76,6 +76,8 @@ textarea { resize: vertical; min-height: 120px; }
|
||||
.load-more-notifications[hidden] { display:none; }
|
||||
.load-more-work { min-height:44px; width:100%; margin-top:10px; }
|
||||
.load-more-work[hidden] { display:none; }
|
||||
.retry-work-route { min-height:44px; width:100%; margin-top:10px; }
|
||||
.retry-work-route[hidden] { display:none; }
|
||||
.my-work-bulk { position:sticky; bottom:0; z-index:4; margin:10px -4px -12px; padding:10px 4px; padding-bottom:calc(10px + env(safe-area-inset-bottom)); background:rgba(11,21,38,.98); border-top:1px solid #2a496e; }
|
||||
.my-work-bulk button { min-height:44px; width:100%; }
|
||||
.my-work[data-stale="true"] { border-color:#fcd34d; }
|
||||
@@ -287,6 +289,7 @@ textarea { resize: vertical; min-height: 120px; }
|
||||
<div class="small" id="notification-page-status" aria-live="polite"></div>
|
||||
<button class="load-more-notifications" id="load-more-notifications" type="button" hidden>Load older updates</button>
|
||||
<div class="small" id="my-work-action-status" aria-live="assertive"></div>
|
||||
<button class="retry-work-route" id="retry-work-route" type="button" hidden>Retry shared work item</button>
|
||||
<div class="small" id="work-route-share-status" aria-live="polite"></div>
|
||||
<div class="my-work-bulk" id="bulk-mark-read-bar" hidden>
|
||||
<button id="bulk-mark-read" type="button"></button>
|
||||
@@ -733,6 +736,17 @@ textarea { resize: vertical; min-height: 120px; }
|
||||
if (!response.ok) throw new Error(payload.error || 'Review request failed.');
|
||||
return payload;
|
||||
}
|
||||
|
||||
async function api(url) {
|
||||
const response = await fetch(url, { headers: { Accept: 'application/json' } });
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
if (!response.ok) {
|
||||
const error = new Error(payload.error || payload.detail || 'Shared work request failed.');
|
||||
error.unavailable = response.status === 404;
|
||||
throw error;
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
const reviewController = createReviewController({ fetchJson: fetchReviewJson, storage: localStorage });
|
||||
const issueController = createIssueSheet({ fetchJson: fetchReviewJson, storage: localStorage });
|
||||
const issueCapture = createIssueCapture({ fetchJson: fetchReviewJson, storage: localStorage });
|
||||
@@ -942,7 +956,22 @@ textarea { resize: vertical; min-height: 120px; }
|
||||
location: window.location,
|
||||
history: window.history,
|
||||
eventTarget: window,
|
||||
resolve: route => {
|
||||
const params = new URLSearchParams({ kind:route.kind });
|
||||
if (route.kind === 'update') params.set('notification_id', route.notification_id);
|
||||
else {
|
||||
params.set('repository', route.repository);
|
||||
params.set('number', route.number);
|
||||
}
|
||||
return api('api/v1/work-route?' + params.toString());
|
||||
},
|
||||
onResolving: () => {
|
||||
qs('#retry-work-route').hidden = true;
|
||||
qs('#my-work-action-status').textContent = 'Loading shared work item…';
|
||||
},
|
||||
onOpen: item => {
|
||||
qs('#retry-work-route').hidden = true;
|
||||
qs('#my-work-action-status').textContent = '';
|
||||
closeOpenWorkSheets();
|
||||
if (item.kind === 'update') notificationReader.open(item, lastMyWork);
|
||||
else if (item.kind === 'review') openReviewSheet(item, reviewTrigger);
|
||||
@@ -956,13 +985,19 @@ textarea { resize: vertical; min-height: 120px; }
|
||||
if (selectedUpdate) closeUpdateSheet(true, false);
|
||||
},
|
||||
onInvalid: () => {
|
||||
qs('#retry-work-route').hidden = true;
|
||||
window.history.replaceState(null, '', window.location.pathname + window.location.search);
|
||||
closeOpenWorkSheets();
|
||||
qs('#my-work-action-status').textContent = 'Route unavailable · this item is no longer in My Work.';
|
||||
qs('#my-work').scrollIntoView({ block:'start' });
|
||||
},
|
||||
onError: () => {
|
||||
qs('#my-work-action-status').textContent = 'Could not load shared work item. The link is preserved; retry when connected.';
|
||||
qs('#retry-work-route').hidden = false;
|
||||
},
|
||||
});
|
||||
workRoute.start();
|
||||
qs('#retry-work-route').addEventListener('click', () => workRoute.sync());
|
||||
|
||||
function closeOpenWorkSheets() {
|
||||
['#issue-sheet', '#pull-sheet', '#review-sheet', '#update-sheet'].forEach(selector =>
|
||||
|
||||
+39
-2
@@ -46,31 +46,68 @@
|
||||
Number(item.number) === route.number;
|
||||
}
|
||||
|
||||
function createController({ location, history, eventTarget, onOpen, onClose, onInvalid }) {
|
||||
function createController({
|
||||
location, history, eventTarget, onOpen, onClose, onInvalid,
|
||||
resolve, onResolving = function () {}, onError = function () {},
|
||||
}) {
|
||||
let items = [];
|
||||
let started = false;
|
||||
let active = '';
|
||||
let ready = false;
|
||||
let resolving = '';
|
||||
let resolution = 0;
|
||||
|
||||
function resolveMissing(fragment, route) {
|
||||
if (typeof resolve !== 'function') {
|
||||
onInvalid();
|
||||
return;
|
||||
}
|
||||
if (resolving === fragment) return;
|
||||
resolving = fragment;
|
||||
const request = ++resolution;
|
||||
onResolving(route);
|
||||
Promise.resolve(resolve(route)).then(item => {
|
||||
if (request !== resolution || String(location.hash || '') !== fragment) return;
|
||||
resolving = '';
|
||||
if (!item || !sameRoute(item, route)) {
|
||||
onInvalid();
|
||||
return;
|
||||
}
|
||||
active = fragment;
|
||||
onOpen({ ...item, kind: route.kind });
|
||||
}).catch(error => {
|
||||
if (request !== resolution || String(location.hash || '') !== fragment) return;
|
||||
resolving = '';
|
||||
if (error?.unavailable) onInvalid();
|
||||
else onError(error, route);
|
||||
});
|
||||
}
|
||||
|
||||
function sync() {
|
||||
const fragment = String(location.hash || '');
|
||||
if (!fragment) {
|
||||
resolution += 1;
|
||||
resolving = '';
|
||||
if (active) onClose();
|
||||
active = '';
|
||||
return;
|
||||
}
|
||||
const route = parse(fragment);
|
||||
if (!route) {
|
||||
resolution += 1;
|
||||
resolving = '';
|
||||
if (fragment.startsWith('#/my-work/')) onInvalid();
|
||||
active = '';
|
||||
return;
|
||||
}
|
||||
const item = items.find(candidate => sameRoute(candidate, route));
|
||||
if (!item) {
|
||||
if (ready) onInvalid();
|
||||
if (ready) resolveMissing(fragment, route);
|
||||
return;
|
||||
}
|
||||
if (active === fragment) return;
|
||||
resolution += 1;
|
||||
resolving = '';
|
||||
active = fragment;
|
||||
onOpen({ ...item, kind: route.kind });
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user