PageSourceSearch

https://www.georgetowncoc.com/assets/controllers/campaign_preview_pane_controller-9ad3b819.js

js georgetowncoc.com collected 2026-09-25 04:04:09 UTC 3,887 bytes, 119 lines download raw bytes

1import { Controller } from "@hotwired/stimulus"
2
3// Renders a live, accurate email preview of the composer form into an iframe (srcdoc),
4// and can send a test of the in-progress draft without saving it first.
5export default class extends Controller {
6  static targets = ["form", "iframe", "status", "testStatus"]
7  static values = { url: String, testUrl: String }
8
9  connect() {
10    this.timer = null
11    this.abortController = null
12    this.requestSeq = 0
13    // Defer the first render so Trix has time to populate its hidden input.
14    this.timer = window.setTimeout(() => this.refresh(), 350)
15  }
16
17  disconnect() {
18    if (this.timer) window.clearTimeout(this.timer)
19    this.abortController?.abort()
20  }
21
22  schedule() {
23    if (this.timer) window.clearTimeout(this.timer)
24    this.setStatus("Updating…")
25    this.timer = window.setTimeout(() => this.refresh(), 450)
26  }
27
28  async refresh() {
29    if (!this.hasFormTarget || !this.hasUrlValue || !this.hasIframeTarget) return
30
31    this.abortController?.abort()
32    this.abortController = new AbortController()
33    const requestId = ++this.requestSeq
34
35    const body = new FormData(this.formTarget)
36    body.delete("_method")
37    body.delete("authenticity_token")
38
39    try {
40      const response = await fetch(this.urlValue, {
41        method: "POST",
42        headers: { Accept: "text/html", "X-CSRF-Token": this.csrfToken() },
43        body,
44        credentials: "same-origin",
45        signal: this.abortController.signal
46      })
47
48      if (!response.ok) throw new Error(`Preview failed (${response.status})`)
49
50      const html = await response.text()
51      if (requestId !== this.requestSeq) return
52
53      this.iframeTarget.srcdoc = html
54      this.setStatus("Updated")
55    } catch (error) {
56      if (error.name === "AbortError") return
57      this.setStatus("Preview unavailable")
58    }
59  }
60
61  // Opens the current draft full-size in a new tab. The live-preview iframe already holds
62  // the rendered email, so we reuse that HTML via a Blob URL — no server round-trip (and
63  // therefore no CSRF concerns), and the window.open happens synchronously on the click so
64  // it is not treated as a blocked popup.
65  openFull(event) {
66    event?.preventDefault()
67
68    const html = (this.hasIframeTarget && this.iframeTarget.srcdoc) ||
69      "<!doctype html><meta charset='utf-8'><body style='font:14px system-ui,sans-serif;padding:24px;color:#52525b'>Start writing to preview your email.</body>"
70
71    const url = URL.createObjectURL(new Blob([html], { type: "text/html" }))
72    window.open(url, "_blank", "noopener")
73    window.setTimeout(() => URL.revokeObjectURL(url), 60000)
74  }
75
76  async sendTest(event) {
77    const button = event?.currentTarget
78    if (!this.hasFormTarget || !this.hasTestUrlValue) return
79
80    if (button) button.disabled = true
81    this.setTestStatus("Sending test…")
82
83    const body = new FormData(this.formTarget)
84    body.delete("_method")
85    body.delete("authenticity_token")
86
87    try {
88      const response = await fetch(this.testUrlValue, {
89        method: "POST",
90        headers: { Accept: "application/json", "X-CSRF-Token": this.csrfToken() },
91        body,
92        credentials: "same-origin"
93      })
94      const payload = await response.json()
95
96      if (!response.ok || payload.ok === false) {
97        throw new Error(payload.error || "Test email could not be sent.")
98      }
99
100      this.setTestStatus(`Test sent to ${payload.to_email}.`)
101    } catch (error) {
102      this.setTestStatus(error.message || "Test email could not be sent.")
103    } finally {
104      if (button) button.disabled = false
105    }
106  }
107
108  setStatus(text) {
109    if (this.hasStatusTarget) this.statusTarget.textContent = text
110  }
111
112  setTestStatus(text) {
113    if (this.hasTestStatusTarget) this.testStatusTarget.textContent = text
114  }
115
116  csrfToken() {
117    return document.querySelector("meta[name='csrf-token']")?.content || ""
118  }
119}

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.