PageSourceSearch

https://www.crawfordcountychamber.org/assets/controllers/site_navigation_live_preview_controller-a7828d16.js

js crawfordcountychamber.org collected 2026-10-04 10:22:48 UTC 4,164 bytes, 135 lines download raw bytes

1import { Controller } from "@hotwired/stimulus"
2
3export default class extends Controller {
4  static targets = ["iframe", "status"]
5  static values = {
6    url: String
7  }
8
9  connect() {
10    this.refreshTimer = null
11    this.abortController = null
12    this.requestSeq = 0
13    this.queueRefresh()
14  }
15
16  disconnect() {
17    this.abortInFlight()
18    if (this.refreshTimer) clearTimeout(this.refreshTimer)
19  }
20
21  queueRefresh(event) {
22    if (!this.shouldHandleEvent(event)) return
23
24    if (this.refreshTimer) clearTimeout(this.refreshTimer)
25    this.refreshTimer = window.setTimeout(() => this.refresh(), 350)
26  }
27
28  refreshNow(event) {
29    event?.preventDefault()
30    this.refresh()
31  }
32
33  async refresh() {
34    if (!this.hasUrlValue || !this.hasIframeTarget) return
35
36    this.abortInFlight()
37    this.abortController = new AbortController()
38    const requestId = ++this.requestSeq
39
40    this.setStatus("Updating preview...")
41
42    const payload = {
43      navigation_items: this.collectNavigationItems(),
44      site: this.collectSiteOptions()
45    }
46
47    try {
48      const response = await fetch(this.urlValue, {
49        method: "POST",
50        headers: {
51          "Accept": "text/html",
52          "Content-Type": "application/json",
53          "X-CSRF-Token": this.csrfToken()
54        },
55        credentials: "same-origin",
56        signal: this.abortController.signal,
57        body: JSON.stringify(payload)
58      })
59
60      if (!response.ok) {
61        throw new Error(`Preview failed (${response.status})`)
62      }
63
64      const html = await response.text()
65      if (requestId !== this.requestSeq) return
66
67      this.iframeTarget.srcdoc = html
68      this.setStatus("Preview updated")
69    } catch (error) {
70      if (error.name === "AbortError") return
71      this.setStatus("Preview unavailable")
72    }
73  }
74
75  collectNavigationItems() {
76    return Array.from(this.element.querySelectorAll("[data-nav-item]")).map((row) => {
77      const id = row.dataset.navItemId
78      // A group's rows nest inside its own, and so do the fields of the "add a
79      // link here" form, so read only the field set stamped with this id.
80      const scope = row.querySelector(`[data-nav-fields="${id}"]`) || row
81      const label = scope.querySelector("input[name='site_navigation_item[label]']")?.value.toString().trim() || ""
82      const kind = scope.querySelector("select[name='site_navigation_item[kind]']")?.value.toString() || "built_in"
83      const builtInKey = scope.querySelector("select[name='site_navigation_item[built_in_key]']")?.value.toString() || ""
84      const pageId = scope.querySelector("select[name='site_navigation_item[page_id]']")?.value.toString() || ""
85      const url = scope.querySelector("input[name='site_navigation_item[url]']")?.value.toString().trim() || ""
86      const visible = scope.querySelector("input[type='checkbox'][name='site_navigation_item[visible]']")?.checked || false
87      const parentId = scope.querySelector("select[name='site_navigation_item[parent_id]']")?.value.toString() || ""
88
89      return {
90        id,
91        label,
92        kind,
93        built_in_key: builtInKey,
94        page_id: pageId,
95        url,
96        visible,
97        parent_id: parentId
98      }
99    })
100  }
101
102  collectSiteOptions() {
103    const showNameInput = this.element.querySelector("input[type='checkbox'][name='site[show_chamber_name_in_header]']")
104    if (!showNameInput) return {}
105
106    return {
107      show_chamber_name_in_header: showNameInput.checked
108    }
109  }
110
111  shouldHandleEvent(event) {
112    if (!event) return true
113    if (event.type === "site-navigation-menu:changed") return true
114
115    const target = event.target
116    if (!(target instanceof HTMLElement)) return false
117    if (target.closest("input[name='site[show_chamber_name_in_header]']")) return true
118    return Boolean(target.closest("[data-nav-item]"))
119  }
120
121  abortInFlight() {
122    if (this.abortController) this.abortController.abort()
123    this.abortController = null
124  }
125
126  setStatus(text) {
127    if (!this.hasStatusTarget) return
128    this.statusTarget.textContent = text
129  }
130
131  csrfToken() {
132    const meta = document.querySelector("meta[name='csrf-token']")
133    return meta ? meta.content : ""
134  }
135}

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.