thunderbird-patch-review
Easy-to-use patch review interface for Thunderbird
Contribute: ~marcc/thunderbird-review-plugin@lists.sr.ht
git clone git://mccd.space/thunderbird-patch-review
| Log | Files | Refs | README | LICENSE |
commit a50534bb5d2f55be7d2f3112fe979aad4e0d81a1 parent 661802884b46373d88e7e52ee3806a87248a5813 Author: Pi Agent <agent@pi.local> Date: Sat, 18 Jul 2026 13:11:52 +0200 review: hide Comment-on-patch button while editing; add Discard drafts When the patch-level comment editor opens, hide the trigger button until the editor is cancelled (post re-renders, dropping it anyway once a draft exists). The button is no longer offered as "Edit comment" when a draft is present — the readonly card (double-click / click to edit) is the only entry point, so there is no redundant modify button after posting. Add a red "Discard drafts…" button in the toolbar, to the left of the comment count and repo path. It asks for confirmation, then clears every draft comment in the series (per-patch Message-ID storage) and re-renders. Diffstat:
| M | extension/review/review.css | | | 14 | ++++++++++++++ |
| M | extension/review/review.html | | | 1 | + |
| M | extension/review/review.js | | | 44 | ++++++++++++++++++++++++++++++++++++-------- |
3 files changed, 51 insertions(+), 8 deletions(-)
diff --git a/extension/review/review.css b/extension/review/review.css
@@ -109,6 +109,20 @@ button {
}
button:hover { border-color: var(--accent); color: var(--accent); }
+
+#btn-discard {
+ border-color: #cf222e;
+ color: #cf222e;
+}
+#btn-discard:hover {
+ background: #cf222e;
+ border-color: #cf222e;
+ color: #fff;
+}
+@media (prefers-color-scheme: dark) {
+ #btn-discard { border-color: #f85149; color: #f85149; }
+ #btn-discard:hover { background: #f85149; border-color: #f85149; color: #0d1117; }
+}
button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
button.primary:hover { color: #fff; opacity: 0.9; }
diff --git a/extension/review/review.html b/extension/review/review.html
@@ -9,6 +9,7 @@
<header id="topbar">
<div id="series-title">Loading…</div>
<div id="actions">
+ <button id="btn-discard" title="Discard all draft comments in this series" hidden>Discard drafts…</button>
<span id="comment-count" title="Draft comments">0 comments</span>
<input id="repo-path" type="text" placeholder="repository path" spellcheck="false">
<button id="btn-browse" title="Choose the repository directory">Browse…</button>
diff --git a/extension/review/review.js b/extension/review/review.js
@@ -139,16 +139,21 @@ function renderPatch() {
? parsed.commitMessage || patch.body
: parsed.commitMessage || patch.title;
msg.appendChild(body);
- const generalBtn = document.createElement("button");
- generalBtn.className = "btn-general";
- generalBtn.textContent = state.comments[i][store.GENERAL] ? "Edit comment" : "Comment on patch";
- generalBtn.addEventListener("click", () => openGeneralEditor(msg));
- msg.appendChild(generalBtn);
+ const existingGeneral = state.comments[i][store.GENERAL];
+ // Only offer "Comment on patch" when there is no draft yet; once a comment
+ // exists the readonly card (double-click / click to edit) is the only
+ // entry point, so we don't also surface an "Edit comment" button.
+ if (!existingGeneral) {
+ const generalBtn = document.createElement("button");
+ generalBtn.className = "btn-general";
+ generalBtn.textContent = "Comment on patch";
+ generalBtn.addEventListener("click", () => openGeneralEditor(msg, generalBtn));
+ msg.appendChild(generalBtn);
+ }
box.appendChild(msg);
- const existingGeneral = state.comments[i][store.GENERAL];
if (existingGeneral) {
- box.appendChild(commentCard(existingGeneral.text, () => openGeneralEditor(msg)));
+ box.appendChild(commentCard(existingGeneral.text, () => openGeneralEditor(msg, null)));
}
content.appendChild(box);
@@ -369,7 +374,7 @@ function findReadonly(lineRow) {
return null;
}
-function openGeneralEditor(anchorEl) {
+function openGeneralEditor(anchorEl, triggerBtn) {
closeEditors();
const existing = state.comments[state.current][store.GENERAL];
// The readonly general comment, if any, is the element right after the
@@ -379,9 +384,17 @@ function openGeneralEditor(anchorEl) {
? anchorEl.nextElementSibling
: null;
if (ro) ro.hidden = true;
+ // While the editor is open the trigger button ("Comment on patch") is
+ // hidden; cancel re-shows it. Post re-renders, which drops it anyway when
+ // a comment now exists.
+ if (triggerBtn) triggerBtn.hidden = true;
+ const restoreTrigger = () => {
+ if (triggerBtn) triggerBtn.hidden = false;
+ };
const box = editorBox(existing ? existing.text : "", store.GENERAL, () => {
box.remove();
if (ro) ro.hidden = false;
+ restoreTrigger();
});
box.classList.add("general-editor");
anchorEl.after(box);
@@ -448,8 +461,23 @@ function editorBox(initial, loc, close) {
function updateCount() {
const total = state.comments.reduce((sum, c) => sum + Object.keys(c).length, 0);
$("#comment-count").textContent = `${total} comment${total === 1 ? "" : "s"}`;
+ // The discard button only makes sense once there are drafts to lose.
+ $("#btn-discard").hidden = total === 0;
}
+$("#btn-discard").addEventListener("click", async () => {
+ const total = state.comments.reduce((sum, c) => sum + Object.keys(c).length, 0);
+ if (total === 0) return;
+ const fill = total === 1 ? `the 1 draft comment` : `all ${total} draft comments`;
+ if (!window.confirm(`Discard ${fill} in this series? This cannot be undone.`)) {
+ return;
+ }
+ await Promise.all(state.series.map((p) => store.clearComments(p.headerMessageId)));
+ state.comments = state.series.map(() => ({}));
+ renderPatch();
+ updateCount();
+});
+
// ---------------------------------------------------------------------------
// Send review / apply series