fix(project): push SSE update when an issue on a board is closed/reopened
Closing or reopening an issue did not notify project boards that carry it as a card, so board tabs showed stale state until a manual reload. CloseIssue/ReopenIssue only published milestone events and the issue timeline notification — nothing project-scoped. Add a CardStateChanged project event, published per linked project from CloseIssue/ReopenIssue (best-effort; never fails the state change). The board frontend flips the issue-state octicon in place and refetches the affected column so state-filtered boards and counts stay correct. The dispatch check precedes the CardUnlinked branch so a close/reopen is not mistaken for a card removal. Also switch a pre-existing String#match to RegExp#exec in the same file to keep it lint-clean. Closes #19
This commit is contained in:
@@ -14,7 +14,7 @@ import {showInfoToast, showWarningToast} from '../modules/toast.ts';
|
||||
// rendered "#index" anchor text and falling back to the internal id.
|
||||
function issueRef(card: HTMLElement | null, issueID: number): string {
|
||||
const idx = card?.querySelector('.issue-card-title, .ref-issue, a[href*="/issues/"]')?.textContent?.trim();
|
||||
const m = idx?.match(/#\d+/);
|
||||
const m = /#\d+/.exec(idx ?? '');
|
||||
return m ? m[0] : `#${issueID}`;
|
||||
}
|
||||
|
||||
@@ -237,6 +237,12 @@ type CardUnlinkedPayload = EventPayloadBase & {
|
||||
issue_id: number;
|
||||
};
|
||||
|
||||
type CardStateChangedPayload = EventPayloadBase & {
|
||||
project_id: number;
|
||||
issue_id: number;
|
||||
is_closed: boolean;
|
||||
};
|
||||
|
||||
type ColumnUpdatedPayload = {
|
||||
project_id: number;
|
||||
column_id: number;
|
||||
@@ -352,6 +358,30 @@ function handleCardUnlinked(board: HTMLElement, payload: CardUnlinkedPayload): v
|
||||
showInfoToast(`${ref} removed from board`);
|
||||
}
|
||||
|
||||
function handleCardStateChanged(board: HTMLElement, payload: CardStateChangedPayload): void {
|
||||
if (payload.session_tag && payload.session_tag === sessionTag) return;
|
||||
const card = board.querySelector<HTMLElement>(`.issue-card[data-issue="${payload.issue_id}"]`);
|
||||
if (!card) return;
|
||||
// Flip the issue state octicon in place (matches templates/shared/issueicon.tmpl).
|
||||
// PR cards carry merged/draft variants we don't recompute here; the column
|
||||
// refetch below keeps state-filtered boards and counts correct regardless.
|
||||
const icon = card.querySelector<SVGElement>('.issue-card-icon svg');
|
||||
if (icon && !icon.classList.contains('octicon-git-pull-request')) {
|
||||
icon.classList.remove('octicon-issue-opened', 'octicon-issue-closed', 'tw-text-green', 'tw-text-red');
|
||||
icon.classList.add(
|
||||
payload.is_closed ? 'octicon-issue-closed' : 'octicon-issue-opened',
|
||||
payload.is_closed ? 'tw-text-red' : 'tw-text-green',
|
||||
);
|
||||
}
|
||||
const ref = issueRef(card, payload.issue_id);
|
||||
// The card's containing column is `#board_{columnID}` (its direct parent).
|
||||
const parent = card.parentElement;
|
||||
if (parent instanceof HTMLElement && parent.id.startsWith('board_')) {
|
||||
refetchColumn(board, Number(parent.id.slice('board_'.length)));
|
||||
}
|
||||
showInfoToast(`${ref} ${payload.is_closed ? 'closed' : 'reopened'}`);
|
||||
}
|
||||
|
||||
function handleColumnCreated(): void {
|
||||
// Rare event; reload is cheap and avoids client-side template duplication.
|
||||
window.location.reload();
|
||||
@@ -423,7 +453,12 @@ function handleProjectDeleted(): void {
|
||||
// types by payload shape; we sniff discriminating fields here. Order
|
||||
// matters: the more specific shapes are checked first.
|
||||
function dispatchProjectEvent(board: HTMLElement, payload: any): void {
|
||||
if ('from_column_id' in payload && 'to_column_id' in payload) {
|
||||
if ('issue_id' in payload && 'is_closed' in payload) {
|
||||
// CardStateChanged: must precede the CardUnlinked branch below, whose
|
||||
// "issue_id and no column_id" shape would otherwise swallow it and
|
||||
// wrongly remove the card on a close/reopen.
|
||||
handleCardStateChanged(board, payload as CardStateChangedPayload);
|
||||
} else if ('from_column_id' in payload && 'to_column_id' in payload) {
|
||||
handleCardMoved(board, payload as CardMovedPayload);
|
||||
} else if ('column_id' in payload && 'issue_id' in payload && 'project_id' in payload) {
|
||||
handleCardLinked(board, payload as CardLinkedPayload);
|
||||
|
||||
Reference in New Issue
Block a user