1import { Controller } from "@hotwired/stimulus"; 2import { 3 clearGlobalInProgressState, 4 decrementGlobalUnreadCount, 5} from "controllers/global_activity_hub/global_count"; 6import { postAlertAction } from "controllers/global_activity_hub/alert_requests"; 7import { 8 forgetAlertOverride, 9 recordAlertDismissed, 10 recordAlertRead, 11} from "controllers/global_activity_hub/local_alert_state"; 12 13export default class extends Controller { 14 static targets = ["unreadDot", "readButton", "dismissButton", "errorDetail", "errorToggle"]; 15 16 // Reveals the failed transfer's technical error in place. The friendly summary is already the 17 // card's message, so this stays collapsed until asked for. 18 toggleError() { 19 if (!this.hasErrorDetailTarget) return; 20 21 const hidden = this.errorDetailTarget.classList.toggle("d-none"); 22 23 if (this.hasErrorToggleTarget) { 24 this.errorToggleTarget.setAttribute("aria-expanded", hidden ? "false" : "true"); 25 const label = this.errorToggleTarget.querySelector("span"); 26 if (label) label.textContent = hidden ? "Show Error" : "Hide Error"; 27 } 28 } 29 30 markRead() { 31 if (!this.element.classList.contains("unread")) return; 32 if (this.element.classList.contains("dismissed")) return; 33 34 this.element.classList.remove("unread"); 35 if (this.hasUnreadDotTarget) this.unreadDotTarget.remove(); 36 if (this.hasReadButtonTarget) this.readButtonTarget.remove(); 37 38 // The card keeps its place in the list but stops counting: the badges track what is still 39 // unread. The baseline is the uncapped server number and cannot be recomputed from the DOM, so 40 // it is decremented here; recount() then repaints from whichever regime is active (ENG-6497). 41 this.notifyTabs((tabs) => { 42 tabs.dropFromBaseline(this.element.dataset.category); 43 tabs.recount(); 44 }); 45 decrementGlobalUnreadCount(1); 46 47 // Before the POST: the inbox poller re-renders from the server, and a tick landing before this 48 // write persists would otherwise bring the unread dot back (ENG-6288). 49 const readId = this.element.dataset.alertId; 50 recordAlertRead(readId); 51 52 postAlertAction(`/global-activity-hub/alerts/${readId}/read`).then((landed) => { 53 if (!landed) forgetAlertOverride(readId, "read"); 54 }); 55 } 56 57 dismiss() { 58 if (this.element.dataset.dismissed === "true") return; 59 const wasUnread = this.element.classList.contains("unread"); 60 61 this.element.classList.add("dismissed"); 62 this.element.dataset.dismissed = "true"; 63 64 this.element.classList.remove("unread"); 65 this.element.dataset.unread = "false"; 66 if (this.hasUnreadDotTarget) this.unreadDotTarget.remove(); 67 if (this.hasReadButtonTarget) this.readButtonTarget.remove(); 68 // The card is now dismissed, so its own Dismiss control is a no-op â drop it so a card left on 69 // screen by "Show dismissed" matches the server render, which withholds the button (ENG-6288). 70 if (this.hasDismissButtonTarget) this.dismissButtonTarget.remove(); 71 72 const container = this.element.closest(".global-activity-hub"); 73 const checkbox = container?.querySelector("#show-dismissed"); 74 if (!checkbox || !checkbox.checked) { 75 this.element.classList.add("d-none-by-dismissed"); 76 } 77 78 if (wasUnread) decrementGlobalUnreadCount(1); 79 80 // After the unread decrement, so the trigger reads the already-updated count. 81 if (this.element.classList.contains("in-progress") && !this.anyRunningCardsLeft()) { 82 clearGlobalInProgressState(); 83 } 84 this.notifyTabs((tabs) => { 85 tabs.refreshDayDividers(); 86 // A dismissed alert stops counting whether or not "Show dismissed" puts it back on screen. 87 // Only an alert that was still OUTSTANDING leaves the baseline â dismissing one already marked 88 // read must not decrement twice (ENG-6497). 89 if (wasUnread) tabs.dropFromBaseline(this.element.dataset.category); 90 tabs.recount(); 91 }); 92 93 // Before the POST, for the same reason as markRead above â otherwise a poll in that window 94 // resurrects the card the reader just cleared (ENG-6288). 95 const dismissId = this.element.dataset.alertId; 96 recordAlertDismissed(dismissId); 97 98 postAlertAction(`/global-activity-hub/alerts/${dismissId}/dismiss`).then((landed) => { 99 if (!landed) forgetAlertOverride(dismissId, "dismissed"); 100 }); 101 } 102 103 anyRunningCardsLeft() { 104 const container = this.element.closest(".global-activity-hub"); 105 if (!container) return false;
106 107 return container.querySelector( 108 ".global-activity-hub-alert-wrapper.in-progress:not(.dismissed)" 109 ) !== null; 110 } 111 112 notifyTabs(callback) { 113 const container = this.element.closest(".global-activity-hub"); 114 if (!container) return; 115 const tabs = this.application.getControllerForElementAndIdentifier( 116 container, 117 "global-activity-hub--tabs" 118 ); 119 if (tabs) callback(tabs); 120 } 121}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.