feat: watch issues from mobile search (Closes #1290)
This commit is contained in:
1 parent
7fbbc2f942
commit
9c187b5ae5
8 files changed
+297
-3
No files matched your search
@@ -5617,6 +5617,7 @@
|
||||
const queueButton = qs('#queue-search-result');
|
||||
const planButton = qs('#plan-search-result');
|
||||
const startButton = qs('#start-search-result');
|
||||
const watchButton = qs('#watch-search-result');
|
||||
const shareButton = qs('#share-search-result');
|
||||
|
||||
qs('#close-search-preview').textContent = searchPreviewReturnKind === 'today-readiness'
|
||||
@@ -5636,6 +5637,8 @@
|
||||
planButton.disabled = false;
|
||||
startButton.hidden = true;
|
||||
startButton.disabled = false;
|
||||
watchButton.hidden = true;
|
||||
watchButton.disabled = false;
|
||||
shareButton.disabled = true;
|
||||
renderSearchPreviewReply(state, null, searchPreview, document);
|
||||
|
||||
@@ -5690,6 +5693,7 @@
|
||||
startButton.textContent = detail.reviewable ? 'Review now' : detail.reopenable ? 'Reopen & resume' :
|
||||
(detail.assigned_to_me ? 'Start in Today' : 'Assign & start');
|
||||
startButton.disabled = state.status === 'claiming' || state.status === 'reopening';
|
||||
renderSearchPreviewWatch(detail, state, watchButton);
|
||||
const shareStatus = {
|
||||
sharing:'Opening share options…', shared:'Search result shared.', copied:'Search result link copied.',
|
||||
'share-canceled':'Share canceled.', 'share-error':'Could not share this result. Try again.',
|
||||
@@ -5698,13 +5702,15 @@
|
||||
else if (state.status === 'reopening') status.textContent = 'Reopening…';
|
||||
else if (state.status === 'claiming') status.textContent = 'Assigning this issue to you…';
|
||||
else if (state.status === 'claimed') status.textContent = 'Assigned. Opening My Work…';
|
||||
else if (state.status.includes('watch')) status.textContent = searchPreviewWatchStatus(state);
|
||||
else if (detail.claimable) status.textContent = 'Open and unassigned.';
|
||||
else if (detail.assigned_to_me) status.textContent = 'Already in My Work.';
|
||||
else if (detail.reopenable) status.textContent = 'Closed—reopen to resume.';
|
||||
else status.textContent = 'Ready.';
|
||||
}
|
||||
const searchSubscription = searchPreviewSubscriptionOptions(fetchReviewJson);
|
||||
const searchPreview = createSearchPreview({
|
||||
fetchJson: item => fetchReviewJson(searchPreviewPath(item), { headers:{ Accept:'application/json' } }),
|
||||
fetchJson:searchSubscription.preview,
|
||||
fetchConversation:(item,page)=>fetchReviewJson(searchPreviewConversationPath(item,page),{
|
||||
headers:{Accept:'application/json'},
|
||||
}),
|
||||
@@ -5713,6 +5719,7 @@
|
||||
'/issues/' + encodeURIComponent(detail.number) + '/' + action,
|
||||
{ method:'PATCH', headers:{ Accept:'application/json' } }
|
||||
),
|
||||
watch:searchSubscription.watch,
|
||||
...searchPreviewReplyOptions(fetchReviewJson, localStorage, globalThis.crypto),
|
||||
queueReply:async (item,body,operationId) => {
|
||||
searchReplyAttachmentTarget = item;
|
||||
@@ -6078,6 +6085,7 @@
|
||||
qs('#share-search-result').addEventListener('click', () => {
|
||||
searchPreview.share(canonicalSearchPreviewUrl()).catch(() => {});
|
||||
});
|
||||
wireSearchPreviewWatch(qs('#watch-search-result'), searchPreview, () => searchPreviewDetail);
|
||||
document.querySelectorAll('.share-work-route').forEach(button => {
|
||||
button.addEventListener('click', async () => {
|
||||
const status = qs('#work-route-share-status');
|
||||
|
||||
+1
-1
@@ -957,7 +957,7 @@
|
||||
<button id="queue-search-result" type="button" hidden>Assign & add to Today</button>
|
||||
<button id="plan-search-result" type="button" hidden>Plan ahead</button>
|
||||
<button id="start-search-result" type="button" hidden>Assign & start</button>
|
||||
|
||||
<button id="watch-search-result" type="button" hidden>Watch issue</button>
|
||||
</div>
|
||||
<button id="share-search-result" type="button" disabled>Share result</button>
|
||||
<a id="open-search-result-gitea" href="#" target="_blank" rel="noopener noreferrer">Open in Gitea</a>
|
||||
|
||||
@@ -32,6 +32,33 @@
|
||||
};
|
||||
root.searchPreviewReplyPath = item => root.searchPreviewPath(item).replace(/\?.*$/, '') +
|
||||
'/comments?kind=' + encodeURIComponent(item.kind);
|
||||
root.searchPreviewSubscriptionPath = item => root.searchPreviewPath(item).replace(/\?.*$/, '') +
|
||||
'/subscription?kind=' + encodeURIComponent(item.kind);
|
||||
root.searchPreviewSubscriptionOptions = fetchJson => ({
|
||||
load:async detail => {
|
||||
if (!(detail.kind === 'issue' && detail.state === 'open' && detail.claimable)) return detail;
|
||||
const result = await fetchJson(root.searchPreviewSubscriptionPath(detail), {headers:{Accept:'application/json'}});
|
||||
return {...detail, watching:result.watching === true};
|
||||
},
|
||||
watch:(detail,watching) => fetchJson(root.searchPreviewSubscriptionPath(detail), {
|
||||
method:watching ? 'PUT' : 'DELETE', headers:{Accept:'application/json'},
|
||||
}),
|
||||
});
|
||||
root.searchPreviewWatchStatus = state => ({
|
||||
watching:'Starting watch…', unwatching:'Stopping watch…',
|
||||
watched:'Watching. Future activity will appear in Updates.',
|
||||
unwatched:'Stopped watching. Assignment and planning are unchanged.',
|
||||
'watch-error':(state.error?.message || 'Watch status was not changed.') + ' Retry.',
|
||||
})[state.status] || '';
|
||||
root.renderSearchPreviewWatch = (detail, state, button) => {
|
||||
button.hidden = !(detail.kind === 'issue' && detail.state === 'open' && detail.claimable);
|
||||
button.textContent = detail.watching ? 'Stop watching' : 'Watch issue';
|
||||
button.disabled = state.status === 'watching' || state.status === 'unwatching';
|
||||
};
|
||||
root.wireSearchPreviewWatch = (button, preview, getDetail) => button.addEventListener('click', () => {
|
||||
const detail = getDetail();
|
||||
if (detail) preview.setWatching(detail.watching !== true).catch(() => {});
|
||||
});
|
||||
root.searchPreviewReplyOptions = (fetchJson, storage, crypto) => ({
|
||||
storage,
|
||||
createOperationId:() => crypto?.randomUUID?.() || String(Date.now()) + '-' + Math.random(),
|
||||
@@ -92,7 +119,7 @@
|
||||
};
|
||||
}
|
||||
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
|
||||
return function createSearchPreview({ fetchJson, fetchConversation, mutate, share, postReply, queueReply, prepareReply, afterReply, hasAttachments, clearAttachments, storage, createOperationId, session, getSession, loadMore, onNavigate, navigationRoot, onState }) {
|
||||
return function createSearchPreview({ fetchJson, fetchConversation, mutate, watch, share, postReply, queueReply, prepareReply, afterReply, hasAttachments, clearAttachments, storage, createOperationId, session, getSession, loadMore, onNavigate, navigationRoot, onState }) {
|
||||
if (Array.isArray(session)) {
|
||||
getSession = session[0];
|
||||
loadMore = () => session[1].loadMore();
|
||||
@@ -105,6 +132,7 @@
|
||||
let shareRequest = null;
|
||||
let moveRequest = null;
|
||||
let replyRequest = null;
|
||||
let watchRequest = null;
|
||||
let conversation = null;
|
||||
|
||||
function sameItem(left, right) {
|
||||
@@ -348,6 +376,23 @@
|
||||
reopen(detail) {
|
||||
return run('reopen', 'reopening', 'reopened', detail);
|
||||
},
|
||||
setWatching(watching) {
|
||||
if (watchRequest) return watchRequest;
|
||||
if (!current || typeof watch !== 'function') {
|
||||
return Promise.reject(new Error('Watching is unavailable.'));
|
||||
}
|
||||
const detail = current;
|
||||
publish({ status:watching ? 'watching' : 'unwatching', item:current, detail });
|
||||
watchRequest = watch(detail, watching).then(result => {
|
||||
current = { ...current, watching:result?.watching === true };
|
||||
publish({ status:watching ? 'watched' : 'unwatched', item:current, detail:current, result });
|
||||
return result;
|
||||
}).catch(error => {
|
||||
publish({ status:'watch-error', item:current, detail, error });
|
||||
throw error;
|
||||
}).finally(() => { watchRequest = null; });
|
||||
return watchRequest;
|
||||
},
|
||||
};
|
||||
if (navigationRoot) {
|
||||
navigationRoot.querySelector('#previous-search-result').addEventListener('click', () => api.previous().catch(() => {}));
|
||||
|
||||
Reference in new issue
Block a user