|
17 | 17 | const RESET_VERSION_KEY = "prrcDefaultResetVersion"; |
18 | 18 | const CURRENT_RESET_VERSION = 1; |
19 | 19 | const BUTTON_ID = "pr-reverse-comments-toggle"; |
20 | | - const AUTOTEST_STATUS_ID = "pr-reverse-comments-autotest-status"; |
| 20 | + const CHECKS_STATUS_ID = "pr-reverse-comments-checks-status"; |
21 | 21 |
|
22 | 22 | // Per-page configuration. `getTargets()` returns an array of |
23 | 23 | // { el, item, descendant } |
|
229 | 229 | document.body.appendChild(btn); |
230 | 230 | } |
231 | 231 |
|
232 | | - function getAutotestTimelineItem() { |
233 | | - const candidates = [ |
234 | | - '[data-testid="issue-viewer-issue-container"] [data-testid^="issue-viewer-comment"]', |
235 | | - '[data-testid="pr-timeline"] [data-testid^="pr-timeline-item"]', |
236 | | - ".js-discussion .js-timeline-item", |
237 | | - ".pull-discussion-timeline .js-timeline-item", |
238 | | - ]; |
239 | | - for (const sel of candidates) { |
240 | | - for (const el of document.querySelectorAll(sel)) { |
241 | | - if ((el.textContent || "").toLowerCase().includes("autotest")) return el; |
242 | | - } |
243 | | - } |
244 | | - return null; |
245 | | - } |
246 | | - |
247 | | - function getAutotestState(text) { |
248 | | - if (/(fail|error|timed out|cancelled|canceled)/i.test(text)) { |
249 | | - return { label: "✗ Autotest failing", color: "#da3633" }; |
250 | | - } |
251 | | - if (/(pass|success|succeed)/i.test(text)) { |
252 | | - return { label: "✓ Autotest passing", color: "#238636" }; |
253 | | - } |
254 | | - if (/(pending|in progress|queued|running)/i.test(text)) { |
255 | | - return { label: "• Autotest running", color: "#9a6700" }; |
256 | | - } |
257 | | - return { label: "• Autotest status", color: "#1f6feb" }; |
258 | | - } |
259 | | - |
260 | | - function getAutotestInsertBeforeNode() { |
| 232 | + // Where to put the checks indicator: at the very top of the conversation |
| 233 | + // column, above the PR description. We insert *before* one of these |
| 234 | + // anchors within its parent. |
| 235 | + function getChecksIndicatorAnchor() { |
261 | 236 | const candidates = [ |
262 | 237 | '[data-testid="issue-viewer-issue-container"] [data-testid="pr-timeline"]', |
263 | 238 | ".js-discussion", |
|
270 | 245 | return null; |
271 | 246 | } |
272 | 247 |
|
273 | | - function injectOrUpdateAutotestIndicator() { |
274 | | - const existing = document.getElementById(AUTOTEST_STATUS_ID); |
| 248 | + function scrollToChecksBox() { |
| 249 | + const box = findChecksBox(); |
| 250 | + if (!box) return; |
| 251 | + box.scrollIntoView({ behavior: "smooth", block: "center" }); |
| 252 | + box.style.outline = "2px solid #1f6feb"; |
| 253 | + box.style.borderRadius = "6px"; |
| 254 | + setTimeout(() => { |
| 255 | + box.style.outline = ""; |
| 256 | + }, 1500); |
| 257 | + } |
| 258 | + |
| 259 | + function injectOrUpdateChecksIndicator() { |
| 260 | + const existing = document.getElementById(CHECKS_STATUS_ID); |
275 | 261 | const cfg = getCurrentPageConfig(); |
276 | 262 | if (!cfg || cfg.name !== "conversation") { |
277 | 263 | if (existing) existing.remove(); |
278 | 264 | return; |
279 | 265 | } |
280 | 266 |
|
281 | | - const target = getAutotestTimelineItem(); |
282 | | - const insertBefore = getAutotestInsertBeforeNode(); |
283 | | - if (!target || !insertBefore || !insertBefore.parentElement) { |
| 267 | + const anchor = getChecksIndicatorAnchor(); |
| 268 | + if (!findChecksBox() || !anchor || !anchor.parentElement) { |
284 | 269 | if (existing) existing.remove(); |
285 | 270 | return; |
286 | 271 | } |
287 | 272 |
|
288 | | - const state = getAutotestState(target.textContent || ""); |
| 273 | + const state = deriveChecksState(getCheckLabels()); |
289 | 274 | const indicator = existing || document.createElement("button"); |
290 | 275 | if (!existing) { |
291 | | - indicator.id = AUTOTEST_STATUS_ID; |
| 276 | + indicator.id = CHECKS_STATUS_ID; |
292 | 277 | indicator.type = "button"; |
293 | 278 | indicator.style.cssText = [ |
294 | 279 | "display: inline-block", |
|
299 | 284 | "font: 12px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif", |
300 | 285 | "cursor: pointer", |
301 | 286 | ].join(";"); |
302 | | - indicator.addEventListener("click", () => { |
303 | | - const freshTarget = getAutotestTimelineItem(); |
304 | | - if (!freshTarget) return; |
305 | | - freshTarget.scrollIntoView({ behavior: "smooth", block: "center" }); |
306 | | - freshTarget.style.outline = "2px solid #1f6feb"; |
307 | | - setTimeout(() => { freshTarget.style.outline = ""; }, 1200); |
308 | | - }); |
| 287 | + indicator.title = "Click to jump to the PR status checks"; |
| 288 | + indicator.addEventListener("click", scrollToChecksBox); |
309 | 289 | } |
310 | 290 |
|
311 | | - indicator.textContent = state.label; |
312 | | - indicator.title = "Click to jump to autotest status in the timeline"; |
313 | | - indicator.style.border = `1px solid ${state.color}`; |
314 | | - indicator.style.color = state.color; |
| 291 | + // Only write to the DOM when the status actually changed; otherwise the |
| 292 | + // body MutationObserver that calls us would see our own text/style |
| 293 | + // mutations and reschedule forever. |
| 294 | + if (indicator.dataset.prrcState !== state.key) { |
| 295 | + indicator.dataset.prrcState = state.key; |
| 296 | + indicator.textContent = state.label; |
| 297 | + indicator.style.border = `1px solid ${state.color}`; |
| 298 | + indicator.style.color = state.color; |
| 299 | + } |
315 | 300 |
|
316 | | - if (indicator !== insertBefore.previousElementSibling) { |
317 | | - insertBefore.parentElement.insertBefore(indicator, insertBefore); |
| 301 | + if (indicator !== anchor.previousElementSibling) { |
| 302 | + anchor.parentElement.insertBefore(indicator, anchor); |
318 | 303 | } |
319 | 304 | } |
320 | 305 |
|
|
345 | 330 | if (!onSupportedPage()) { |
346 | 331 | const btn = document.getElementById(BUTTON_ID); |
347 | 332 | if (btn) btn.remove(); |
348 | | - const autotest = document.getElementById(AUTOTEST_STATUS_ID); |
349 | | - if (autotest) autotest.remove(); |
| 333 | + const checks = document.getElementById(CHECKS_STATUS_ID); |
| 334 | + if (checks) checks.remove(); |
350 | 335 | disconnectObservers(); |
351 | 336 | activeTargets = []; |
352 | 337 | return; |
|
355 | 340 | if (!document.getElementById(BUTTON_ID)) { |
356 | 341 | injectToggleButton(); |
357 | 342 | } |
358 | | - injectOrUpdateAutotestIndicator(); |
| 343 | + injectOrUpdateChecksIndicator(); |
359 | 344 |
|
360 | 345 | const cfg = getCurrentPageConfig(); |
361 | 346 | const freshTargets = cfg.getTargets(); |
|
394 | 379 | startBodyWatcher(); |
395 | 380 | if (onSupportedPage()) { |
396 | 381 | injectToggleButton(); |
397 | | - injectOrUpdateAutotestIndicator(); |
| 382 | + injectOrUpdateChecksIndicator(); |
398 | 383 | } |
399 | 384 | scheduleRebindIfNeeded(); |
400 | 385 | } |
|
0 commit comments