feat: continue unread updates offline (#347)
This commit is contained in:
1 parent
0539b0a035
commit
ae6cde29c2
11 files changed
+161
-14
No files matched your search
+31
-3
@@ -429,6 +429,7 @@
|
||||
qs('#send-update-reply').disabled = false;
|
||||
qs('#update-ownership-action').hidden = true;
|
||||
qs('#retry-update-load').hidden = true;
|
||||
setOfflineUpdateControls(false);
|
||||
qs('#keep-update-unread').focus();
|
||||
},
|
||||
onDetail: detail => {
|
||||
@@ -437,7 +438,14 @@
|
||||
qs('#update-subject-state').textContent = detail.state || '';
|
||||
qs('#update-subject-body').innerHTML = renderMarkdown(detail.subject_body || 'No subject context was provided.');
|
||||
qs('#open-update-gitea').href = detail.url || selectedUpdate?.url || '#';
|
||||
updateOwnership.open(detail, selectedUpdate);
|
||||
if (offlineWorkMode) {
|
||||
setOfflineUpdateControls(true);
|
||||
} else {
|
||||
updateOwnership.open(detail, selectedUpdate);
|
||||
if (offlineWorkStore.enabled() && confirmedOwnerLogin && selectedUpdate) {
|
||||
offlineWorkStore.saveDetail(confirmedOwnerLogin, selectedUpdate, detail);
|
||||
}
|
||||
}
|
||||
qs('#retry-update-load').hidden = true;
|
||||
},
|
||||
onConversation: renderUpdateConversation,
|
||||
@@ -470,7 +478,8 @@
|
||||
const offlineLogin = planningOwnerLogin || confirmedOwnerLogin ||
|
||||
String(offlineWorkStore.load()?.user?.login || '').trim();
|
||||
const savedDetail = offlineWorkStore.loadDetail(offlineLogin, item);
|
||||
if (!todayWork.contains(item) || !savedDetail) {
|
||||
const savedUpdate = item.kind === 'update' && item.has_update;
|
||||
if ((!savedUpdate && !todayWork.contains(item)) || !savedDetail) {
|
||||
qs('#my-work-action-status').textContent = 'Details not saved—reconnect to open this item.';
|
||||
return;
|
||||
}
|
||||
@@ -480,6 +489,14 @@
|
||||
} else if (item.kind === 'pull' && !item.is_review) {
|
||||
pullTrigger = trigger;
|
||||
openPullSheet(item, trigger, savedDetail);
|
||||
} else if (savedUpdate) {
|
||||
updateTrigger = trigger;
|
||||
notificationReader.open(item, savedDetail).then(opened => {
|
||||
if (opened && selectedUpdate === item) {
|
||||
qs('#update-sheet-status').textContent = 'Offline update · saved ' + fmt(savedDetail.saved_at) +
|
||||
' · replies queue for sync. Reconnect to mark read, take ownership, defer, or load older messages.';
|
||||
}
|
||||
});
|
||||
} else {
|
||||
qs('#my-work-action-status').textContent = 'Details not saved—reconnect to open this item.';
|
||||
}
|
||||
@@ -634,6 +651,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
function setOfflineUpdateControls(offline) {
|
||||
qs('#mark-update-read-next').disabled = offline;
|
||||
qs('#update-ownership-action').disabled = offline;
|
||||
qs('#load-older-update-comments').disabled = offline;
|
||||
qs('#update-sheet .detail-defer').inert = offline;
|
||||
}
|
||||
|
||||
function renderContextSnapshot(data) {
|
||||
liveMode = true;
|
||||
hasContextSnapshot = true;
|
||||
@@ -959,7 +983,7 @@
|
||||
const item = lastMyWork[Number(button.dataset.updateIndex)];
|
||||
if (!item) return;
|
||||
updateTrigger = button;
|
||||
workRoute.open({ ...item, kind:'update' });
|
||||
openRoutedWork(item, button);
|
||||
});
|
||||
});
|
||||
document.querySelectorAll('[data-notification-id]').forEach(button => {
|
||||
@@ -3018,6 +3042,10 @@
|
||||
offlineWorkMode = value;
|
||||
['#find-work', '#start-work-session', '#load-more-work', '#load-more-notifications', '#bulk-mark-read']
|
||||
.forEach(selector => { const button = qs(selector); if (button) button.disabled = value; });
|
||||
if (value) {
|
||||
document.querySelectorAll('[data-notification-id], [data-later-preset], [data-today-add]')
|
||||
.forEach(button => { button.disabled = true; });
|
||||
}
|
||||
}
|
||||
function hydrateOfflineWork(mode = 'offline') {
|
||||
const saved = offlineWorkStore.load();
|
||||
|
||||
+6
-4
@@ -288,14 +288,16 @@ function createNotificationReader({
|
||||
let loadVersion = 0;
|
||||
let marking = false;
|
||||
let conversationPager = null;
|
||||
let offlineHydrated = false;
|
||||
|
||||
async function open(item) {
|
||||
async function open(item, savedDetail = null) {
|
||||
selected = item;
|
||||
offlineHydrated = Boolean(savedDetail && !Array.isArray(savedDetail));
|
||||
const version = ++loadVersion;
|
||||
onOpen(item);
|
||||
onStatus('Loading update…');
|
||||
try {
|
||||
const detail = await load(item.notification_id);
|
||||
const detail = offlineHydrated ? savedDetail : await load(item.notification_id);
|
||||
if (selected !== item || version !== loadVersion) return false;
|
||||
onDetail(detail);
|
||||
if (createPager && loadConversation && detail.conversation) {
|
||||
@@ -324,7 +326,7 @@ function createNotificationReader({
|
||||
return true;
|
||||
},
|
||||
async loadOlder() {
|
||||
if (!conversationPager || !selected) return false;
|
||||
if (!conversationPager || !selected || offlineHydrated) return false;
|
||||
const pager = conversationPager;
|
||||
const version = loadVersion;
|
||||
onStatus('Loading older messages…');
|
||||
@@ -342,7 +344,7 @@ function createNotificationReader({
|
||||
}
|
||||
},
|
||||
async markReadAndNext(items) {
|
||||
if (!selected || marking) return false;
|
||||
if (!selected || marking || offlineHydrated) return false;
|
||||
const current = selected;
|
||||
marking = true;
|
||||
onStatus('Marking update read…');
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
];
|
||||
const DETAIL_FIELDS = [
|
||||
'title', 'body', 'state', 'labels', 'assignees', 'author', 'url', 'due_date', 'milestone',
|
||||
'id', 'repository', 'subject_type', 'subject_body',
|
||||
];
|
||||
const COMMENT_FIELDS = ['id', 'author', 'body', 'created_at', 'updated_at', 'url'];
|
||||
|
||||
@@ -48,6 +49,10 @@
|
||||
}
|
||||
|
||||
function detailKey(item) {
|
||||
if (item?.kind === 'update') {
|
||||
const notificationId = Number(item?.notification_id || 0);
|
||||
return Number.isInteger(notificationId) && notificationId > 0 ? 'update:' + notificationId : '';
|
||||
}
|
||||
const kind = item?.kind === 'pull' ? 'pull' : item?.kind === 'issue' ? 'issue' : '';
|
||||
const repository = String(item?.repository || '');
|
||||
const number = Number(item?.number || 0);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const BASE = new URL('./', self.location.href).pathname;
|
||||
importScripts(BASE + 'static/background-issue-sync.js');
|
||||
const CACHE = 'stackchain-dashboard-shell-v39';
|
||||
const CACHE = 'stackchain-dashboard-shell-v40';
|
||||
const OUTAGE_STATUSES = new Set([500, 502, 503, 504]);
|
||||
const SHELL = [
|
||||
BASE,
|
||||
|
||||
Reference in new issue
Block a user