Skip to content

Commit 57bbda6

Browse files
committed
Add action buttons back
1 parent e403e75 commit 57bbda6

File tree

2 files changed

+186
-0
lines changed

2 files changed

+186
-0
lines changed

assets/app.js

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,25 @@ const App = (() => {
397397
${reviewers}
398398
</div>
399399
</div>
400+
<div class="pr-actions">
401+
<button class="pr-action-btn" data-action="merge" data-pr-id="${pr.id}" title="Merge PR">
402+
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
403+
<path d="M5 3.25a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm0 9.5a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0zm8.25-6.5a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z"/>
404+
<path d="M1.75 5.5v5a.75.75 0 001.5 0v-5a.75.75 0 00-1.5 0zm6.5-3.25a.75.75 0 000 1.5h1.5v2.5a2.25 2.25 0 01-2.25 2.25h-1a.75.75 0 000 1.5h1a3.75 3.75 0 003.75-3.75v-2.5h1.5a.75.75 0 000-1.5h-5z"/>
405+
</svg>
406+
</button>
407+
<button class="pr-action-btn" data-action="unassign" data-pr-id="${pr.id}" title="Unassign">
408+
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
409+
<path d="M10.5 5a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0zm.514 2.63a4 4 0 10-6.028 0A4.002 4.002 0 002 11.5V13a1 1 0 001 1h10a1 1 0 001-1v-1.5a4.002 4.002 0 00-2.986-3.87zM8 1a3 3 0 100 6 3 3 0 000-6zM3 11.5A3 3 0 016 8.5h4a3 3 0 013 3V13H3v-1.5z"/>
410+
<path d="M12.146 5.146a.5.5 0 01.708 0l2 2a.5.5 0 010 .708l-2 2a.5.5 0 01-.708-.708L13.293 8l-1.147-1.146a.5.5 0 010-.708z"/>
411+
</svg>
412+
</button>
413+
<button class="pr-action-btn" data-action="close" data-pr-id="${pr.id}" title="Close PR">
414+
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
415+
<path d="M3.72 3.72a.75.75 0 011.06 0L8 6.94l3.22-3.22a.75.75 0 111.06 1.06L9.06 8l3.22 3.22a.75.75 0 11-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 01-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 010-1.06z"/>
416+
</svg>
417+
</button>
418+
</div>
400419
</div>
401420
`;
402421
};
@@ -488,6 +507,118 @@ const App = (() => {
488507
hide(emptyState);
489508
}
490509
};
510+
511+
const handlePRAction = async (action, prId) => {
512+
// Find PR in all sections
513+
const allPRs = [
514+
...state.pullRequests.incoming,
515+
...state.pullRequests.outgoing,
516+
...state.pullRequests.drafts
517+
];
518+
const pr = allPRs.find(p => p.id.toString() === prId);
519+
if (!pr) return;
520+
521+
const token = getStoredToken();
522+
if (!token) {
523+
showToast('Please login to perform this action', 'error');
524+
return;
525+
}
526+
527+
try {
528+
let response;
529+
const headers = {
530+
'Authorization': `token ${token}`,
531+
'Accept': 'application/vnd.github.v3+json'
532+
};
533+
534+
switch (action) {
535+
case 'merge':
536+
response = await fetch(`${CONFIG.API_BASE}/repos/${pr.repository.full_name}/pulls/${pr.number}/merge`, {
537+
method: 'PUT',
538+
headers: {
539+
...headers,
540+
'Content-Type': 'application/json'
541+
},
542+
body: JSON.stringify({
543+
commit_title: `Merge pull request #${pr.number} from ${pr.head.ref}`,
544+
commit_message: pr.title
545+
})
546+
});
547+
548+
if (response.ok) {
549+
showToast('PR merged successfully', 'success');
550+
// Remove PR from state
551+
['incoming', 'outgoing', 'drafts'].forEach(section => {
552+
const index = state.pullRequests[section].findIndex(p => p.id.toString() === prId);
553+
if (index !== -1) {
554+
state.pullRequests[section].splice(index, 1);
555+
}
556+
});
557+
// Update the display
558+
updatePRSections();
559+
} else {
560+
const error = await response.json();
561+
showToast(error.message || 'Failed to merge PR', 'error');
562+
}
563+
break;
564+
565+
case 'unassign':
566+
response = await fetch(`${CONFIG.API_BASE}/repos/${pr.repository.full_name}/issues/${pr.number}/assignees`, {
567+
method: 'DELETE',
568+
headers: {
569+
...headers,
570+
'Content-Type': 'application/json'
571+
},
572+
body: JSON.stringify({
573+
assignees: pr.assignees?.map(a => a.login) || []
574+
})
575+
});
576+
577+
if (response.ok) {
578+
showToast('Unassigned from PR', 'success');
579+
// Refresh the PR list
580+
updatePRSections();
581+
} else {
582+
showToast('Failed to unassign', 'error');
583+
}
584+
break;
585+
586+
case 'close':
587+
response = await fetch(`${CONFIG.API_BASE}/repos/${pr.repository.full_name}/pulls/${pr.number}`, {
588+
method: 'PATCH',
589+
headers: {
590+
...headers,
591+
'Content-Type': 'application/json'
592+
},
593+
body: JSON.stringify({
594+
state: 'closed'
595+
})
596+
});
597+
598+
if (response.ok) {
599+
showToast('PR closed', 'success');
600+
// Remove PR from state
601+
['incoming', 'outgoing', 'drafts'].forEach(section => {
602+
const index = state.pullRequests[section].findIndex(p => p.id.toString() === prId);
603+
if (index !== -1) {
604+
state.pullRequests[section].splice(index, 1);
605+
}
606+
});
607+
// Update the display
608+
updatePRSections();
609+
} else {
610+
const errorMsg = response.status === 403 ?
611+
'Failed to close PR - Permission denied' :
612+
'Failed to close PR';
613+
showToast(errorMsg, 'error');
614+
}
615+
break;
616+
}
617+
} catch (error) {
618+
console.error('Error performing PR action:', error);
619+
showToast('An error occurred', 'error');
620+
}
621+
};
491622

492623
const handleKeyboardShortcuts = e => {
493624
if (e.target.matches('input, textarea')) return;
@@ -699,6 +830,16 @@ const App = (() => {
699830

700831
document.addEventListener('keydown', handleKeyboardShortcuts);
701832

833+
// Add event delegation for PR action buttons
834+
document.addEventListener('click', (e) => {
835+
if (e.target.closest('.pr-action-btn')) {
836+
const btn = e.target.closest('.pr-action-btn');
837+
const action = btn.dataset.action;
838+
const prId = btn.dataset.prId;
839+
handlePRAction(action, prId);
840+
}
841+
});
842+
702843
// Check for OAuth callback
703844
if (urlParams.get('code')) {
704845
handleOAuthCallback();

assets/styles.css

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,51 @@ a {
618618
margin-left: var(--space-1);
619619
}
620620

621+
/* PR Action Buttons */
622+
.pr-actions {
623+
position: absolute;
624+
bottom: var(--space-2);
625+
right: calc(var(--space-2) + 40px); /* Move left to avoid covering age text */
626+
display: flex;
627+
gap: var(--space-1);
628+
opacity: 0;
629+
transition: opacity var(--transition);
630+
}
631+
632+
.pr-card:hover .pr-actions {
633+
opacity: 1;
634+
}
635+
636+
.pr-action-btn {
637+
background: rgba(243, 244, 246, 0.8);
638+
border: 1px solid rgba(209, 213, 219, 0.5);
639+
border-radius: var(--radius-sm);
640+
padding: var(--space-1);
641+
cursor: pointer;
642+
color: var(--color-text-tertiary);
643+
transition: all var(--transition);
644+
display: flex;
645+
align-items: center;
646+
justify-content: center;
647+
width: 28px;
648+
height: 28px;
649+
}
650+
651+
.pr-action-btn:hover {
652+
background: rgba(229, 231, 235, 0.9);
653+
border-color: rgba(156, 163, 175, 0.7);
654+
color: var(--color-text-secondary);
655+
}
656+
657+
.pr-action-btn:active {
658+
transform: scale(0.95);
659+
}
660+
661+
.pr-action-btn svg {
662+
width: 16px;
663+
height: 16px;
664+
}
665+
621666
/* Empty State */
622667
.empty-state {
623668
text-align: center;

0 commit comments

Comments
 (0)