PageSourceSearch

https://my.pressable.com/assets/controllers/global_activity_hub/alert_controller-7aa93eb9.js

js pressable.com collected 2026-09-24 15:23:50 UTC 4,888 bytes, 121 lines download raw bytes

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.