feat: comment and continue Today work (#429)
This commit is contained in:
1 parent
43595de52f
commit
2cbeba30fd
14 files changed
+370
-19
No files matched your search
@@ -0,0 +1,48 @@
|
||||
function createCommentNext({ post, queue, canQueue, accept = () => undefined, complete }) {
|
||||
let inFlight = null;
|
||||
|
||||
function submit(item, body, operationId = '') {
|
||||
if (inFlight) return inFlight;
|
||||
inFlight = (async () => {
|
||||
try {
|
||||
let comment;
|
||||
try {
|
||||
comment = await post(item, body);
|
||||
} catch (error) {
|
||||
if (!canQueue(error)) throw error;
|
||||
const admission = await queue({
|
||||
kind: item.kind === 'pull' ? 'pull-comment' : 'issue-comment',
|
||||
repository: item.repository,
|
||||
number: item.number,
|
||||
body,
|
||||
operationId: typeof operationId === 'function' ? operationId() : operationId,
|
||||
});
|
||||
if (!admission || (!admission.item && admission.durable !== true)) {
|
||||
throw new Error('Comment was not saved for delivery.');
|
||||
}
|
||||
accept(item, { delivery: admission.background ? 'queued' : 'saved' });
|
||||
return {
|
||||
accepted: true,
|
||||
delivery: admission.background ? 'queued' : 'saved',
|
||||
background: Boolean(admission.background),
|
||||
completed: Boolean(complete(item)),
|
||||
};
|
||||
}
|
||||
accept(item, { delivery: 'posted', comment });
|
||||
return {
|
||||
accepted: true,
|
||||
delivery: 'posted',
|
||||
comment,
|
||||
completed: Boolean(complete(item)),
|
||||
};
|
||||
} finally {
|
||||
inFlight = null;
|
||||
}
|
||||
})();
|
||||
return inFlight;
|
||||
}
|
||||
|
||||
return { submit, busy: () => Boolean(inFlight) };
|
||||
}
|
||||
|
||||
if (typeof module !== 'undefined' && module.exports) module.exports = createCommentNext;
|
||||
@@ -226,6 +226,8 @@ textarea { resize: vertical; min-height: 120px; }
|
||||
.issue-comment { padding:10px 0; border-bottom:1px solid #1b2d45; }
|
||||
.issue-comment-composer { display:grid; gap:8px; margin-top:16px; }
|
||||
.issue-comment-composer button { min-height:44px; width:100%; }
|
||||
.comment-actions { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:8px; }
|
||||
.comment-actions button { min-height:44px; width:100%; }
|
||||
.issue-label-editor { max-width:100%; margin:14px 0; padding:12px; border:1px solid #2a496e; border-radius:12px; }
|
||||
.issue-label-list { display:grid; grid-template-columns:repeat(auto-fit,minmax(min(180px,100%),1fr)); gap:8px; max-width:100%; }
|
||||
.issue-label-option { min-height:44px; max-width:100%; display:flex; align-items:center; gap:10px; padding:8px; border:1px solid #2a496e; border-radius:10px; overflow-wrap:anywhere; }
|
||||
|
||||
@@ -782,6 +782,44 @@
|
||||
warm: warmTodayOffline,
|
||||
announce: message => { qs('#my-work-action-status').textContent = message; },
|
||||
});
|
||||
function setCommentNextVisibility(kind) {
|
||||
const item = kind === 'issue' ? selectedIssue : selectedPull;
|
||||
qs('#send-' + kind + '-comment-next').hidden = !item || !workSession.checkpointed(item);
|
||||
}
|
||||
const issueCommentNext = createCommentNext({
|
||||
post: async (item, body) => {
|
||||
const comment = await issueController.comment(item, body);
|
||||
if (selectedIssue === item && issueConversation) renderIssueConversation(issueConversation.append(comment));
|
||||
return comment;
|
||||
},
|
||||
queue: message => authoredOutbox.enqueueDurably(message),
|
||||
canQueue: canQueueMessage,
|
||||
accept: item => {
|
||||
issueController.saveDraft(item, '');
|
||||
if (selectedIssue === item) qs('#issue-comment').value = '';
|
||||
},
|
||||
complete: item => completeTodayItem(item, {
|
||||
successMessage: 'Comment saved. Next Today item opened.',
|
||||
failureMessage: 'Comment saved, but Today still needs completion.',
|
||||
}),
|
||||
});
|
||||
const pullCommentNext = createCommentNext({
|
||||
post: async (item, body) => {
|
||||
const comment = await pullController.comment(item, body);
|
||||
if (selectedPull === item && pullConversation) renderPullConversation(pullConversation.append(comment));
|
||||
return comment;
|
||||
},
|
||||
queue: message => authoredOutbox.enqueueDurably(message),
|
||||
canQueue: canQueueMessage,
|
||||
accept: item => {
|
||||
pullController.saveDraft(item, '');
|
||||
if (selectedPull === item) qs('#pull-comment').value = '';
|
||||
},
|
||||
complete: item => completeTodayItem(item, {
|
||||
successMessage: 'Comment saved. Next Today item opened.',
|
||||
failureMessage: 'Comment saved, but Today still needs completion.',
|
||||
}),
|
||||
});
|
||||
const closeOfflineIssue = createOfflineIssueClose({
|
||||
enqueueDurably: message => authoredOutbox.enqueueDurably(message),
|
||||
completeToday: (item, options) => completeTodayItem(item, options),
|
||||
@@ -1732,6 +1770,7 @@
|
||||
qs('#retry-issue-load').hidden = true;
|
||||
qs('#open-issue-gitea').href = item.url || '#';
|
||||
qs('#send-issue-comment').disabled = false;
|
||||
setCommentNextVisibility('issue');
|
||||
qs('#edit-issue-content').disabled = true;
|
||||
qs('#issue-edit-form').hidden = true;
|
||||
qs('#issue-edit-status').textContent = '';
|
||||
@@ -1894,6 +1933,7 @@
|
||||
qs('#merge-pull').textContent = workSession.active() ? 'Merge & next' : 'Merge';
|
||||
qs('#retry-pull-load').hidden = true;
|
||||
qs('#open-pull-gitea').href = item.url || '#';
|
||||
setCommentNextVisibility('pull');
|
||||
qs('#close-pull-sheet').focus();
|
||||
try {
|
||||
const detail = offlineDetail || await pullController.load(item);
|
||||
@@ -3351,6 +3391,43 @@
|
||||
qs('#issue-milestone').disabled = false;
|
||||
}
|
||||
});
|
||||
async function submitCommentAndNext(kind) {
|
||||
const item = kind === 'issue' ? selectedIssue : selectedPull;
|
||||
if (!item || !workSession.checkpointed(item)) return;
|
||||
const textarea = qs('#' + kind + '-comment');
|
||||
const status = qs('#' + kind + '-comment-status');
|
||||
const body = textarea.value.trim();
|
||||
if (!body) {
|
||||
status.textContent = 'Write a comment before posting.';
|
||||
textarea.focus();
|
||||
return;
|
||||
}
|
||||
const postButton = qs('#send-' + kind + '-comment');
|
||||
const nextButton = qs('#send-' + kind + '-comment-next');
|
||||
const controller = kind === 'issue' ? issueCommentNext : pullCommentNext;
|
||||
const operationId = () => localStorage.getItem('stackchain.' + kind + '-comment.v1:' +
|
||||
item.repository + '#' + item.number + ':operation');
|
||||
postButton.disabled = true;
|
||||
nextButton.disabled = true;
|
||||
status.textContent = 'Posting comment and opening next…';
|
||||
try {
|
||||
const result = await controller.submit(item, body, operationId);
|
||||
const stillOpen = kind === 'issue' ? selectedIssue === item : selectedPull === item;
|
||||
if (!stillOpen) return;
|
||||
if (!result.completed) status.textContent = 'Comment saved, but Today still needs completion.';
|
||||
else if (result.delivery === 'posted') status.textContent = 'Comment posted.';
|
||||
else if (result.background) status.textContent = 'Queued for sync when the connection returns.';
|
||||
else status.textContent = 'Saved for next launch; background delivery unavailable.';
|
||||
} catch (error) {
|
||||
status.textContent = error.message + ' Your draft and Today position are safe; retry.';
|
||||
textarea.focus();
|
||||
} finally {
|
||||
postButton.disabled = false;
|
||||
nextButton.disabled = false;
|
||||
}
|
||||
}
|
||||
qs('#send-issue-comment-next').addEventListener('click', () => submitCommentAndNext('issue'));
|
||||
qs('#send-pull-comment-next').addEventListener('click', () => submitCommentAndNext('pull'));
|
||||
qs('#send-issue-comment').addEventListener('click', async () => {
|
||||
if (!selectedIssue) return;
|
||||
const body = qs('#issue-comment').value.trim();
|
||||
|
||||
+9
-2
@@ -269,7 +269,10 @@
|
||||
<section class="issue-comment-composer" aria-labelledby="issue-comment-title">
|
||||
<h2 id="issue-comment-title">Add comment</h2>
|
||||
<textarea id="issue-comment" maxlength="10000" placeholder="Write a comment"></textarea>
|
||||
<button id="send-issue-comment" type="button">Post comment</button>
|
||||
<div class="comment-actions">
|
||||
<button id="send-issue-comment" type="button">Post comment</button>
|
||||
<button id="send-issue-comment-next" type="button" hidden>Comment & next</button>
|
||||
</div>
|
||||
<div id="issue-comment-status" class="small" aria-live="assertive"></div>
|
||||
</section>
|
||||
<details class="issue-planning" id="issue-planning">
|
||||
@@ -470,7 +473,10 @@
|
||||
<section class="pull-comment-composer" aria-labelledby="pull-comment-title">
|
||||
<h2 id="pull-comment-title">Add comment</h2>
|
||||
<textarea id="pull-comment" maxlength="10000" placeholder="Write a comment"></textarea>
|
||||
<button id="send-pull-comment" type="button">Post comment</button>
|
||||
<div class="comment-actions">
|
||||
<button id="send-pull-comment" type="button">Post comment</button>
|
||||
<button id="send-pull-comment-next" type="button" hidden>Comment & next</button>
|
||||
</div>
|
||||
<div id="pull-comment-status" class="small" aria-live="assertive"></div>
|
||||
</section>
|
||||
<details class="pull-review" id="pull-review">
|
||||
@@ -602,6 +608,7 @@
|
||||
<script src="static/card-planning.js"></script>
|
||||
<script src="static/today-work.js"></script>
|
||||
<script src="static/today-completion.js"></script>
|
||||
<script src="static/comment-next.js"></script>
|
||||
<script src="static/plan-today.js"></script>
|
||||
<script src="static/plan-today-preview.js"></script>
|
||||
<script src="static/today-sync.js"></script>
|
||||
|
||||
+1
-1
@@ -563,7 +563,7 @@ function createWorkSession({
|
||||
|
||||
return {
|
||||
active: () => running,
|
||||
checkpointed: () => running && durable,
|
||||
checkpointed: item => running && durable && (!item || workIdentity(item) === currentIdentity),
|
||||
end: () => finish(),
|
||||
resumable: () => Boolean(checkpoint?.read()),
|
||||
reopen() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const BASE = new URL('./', self.location.href).pathname;
|
||||
importScripts(BASE + 'static/background-issue-sync.js');
|
||||
const CACHE = 'stackchain-dashboard-shell-v71';
|
||||
const CACHE = 'stackchain-dashboard-shell-v72';
|
||||
const OFFLINE_LEASE_URL = new URL(BASE + '__offline-session-lease', self.location.origin).href;
|
||||
const OUTAGE_STATUSES = new Set([500, 502, 503, 504]);
|
||||
const NAVIGATION_TIMEOUT_MS = self.__STACKCHAIN_NAVIGATION_TIMEOUT_MS || 4000;
|
||||
@@ -29,6 +29,7 @@ const SHELL = [
|
||||
BASE + 'static/card-planning.js',
|
||||
BASE + 'static/today-work.js',
|
||||
BASE + 'static/today-completion.js',
|
||||
BASE + 'static/comment-next.js',
|
||||
BASE + 'static/plan-today.js',
|
||||
BASE + 'static/plan-today-preview.js',
|
||||
BASE + 'static/today-sync.js',
|
||||
|
||||
Reference in new issue
Block a user