PageSourceSearch

https://app.wpml.org/assets/controllers/support_search_controller-d65ad6c2.js

js wpml.org collected 2026-09-24 06:48:41 UTC 2,740 bytes, 77 lines download raw bytes

1import { Controller } from "@hotwired/stimulus"
2
3// #200 (Amir 2026-07-06): instant same-page results for the support search — the client's OWN
4// tickets + documentation (harvested index now; ma-10002's site-content-index.json later — see
5// docs/specs/STATIC-SITE-CONTRACT.md). ↑/↓ + Enter opens the highlighted result; Esc closes.
6// No submit anywhere: the old Search-button navigation to wpml.org is retired.
7export default class extends Controller {
8  static targets = ["input", "list"]
9  static values = { url: String }
10
11  connect() {
12    this.timer = null
13    this.highlighted = -1
14  }
15
16  query() {
17    clearTimeout(this.timer)
18    const q = this.inputTarget.value.trim()
19    if (q.length < 3) return this.close()
20    this.timer = setTimeout(async () => {
21      try {
22        const res = await fetch(`${this.urlValue}?q=${encodeURIComponent(q)}`, { headers: { Accept: "application/json" } })
23        const data = await res.json()
24        this.render(data.results || [])
25      } catch { this.close() }
26    }, 200)
27  }
28
29  render(results) {
30    this.highlighted = -1
31    if (!results.length) {
32      this.listTarget.innerHTML = `<li class="px-4 py-3 text-sm text-gray-500">No matches</li>`
33      this.listTarget.hidden = false
34      return
35    }
36    this.listTarget.innerHTML = results.map(r => `
37      <li>
38        <a href="${r.url}" class="flex items-center justify-between gap-3 px-4 py-2.5 text-sm text-gray-800 hover:bg-brand-soft" data-support-search-target="item">
39          <span class="truncate">${this.escape(r.title)}</span>
40          <span class="shrink-0 rounded bg-gray-100 px-1.5 py-0.5 text-[10px] font-medium uppercase tracking-wide text-gray-500">${this.escape(r.type)}</span>
41        </a>
42      </li>`).join("")
43    this.listTarget.hidden = false
44  }
45
46  keydown(event) {
47    if (this.listTarget.hidden) return
48    const items = this.listTarget.querySelectorAll("a")
49    if (event.key === "ArrowDown" || event.key === "ArrowUp") {
50      event.preventDefault()
51      this.highlighted += event.key === "ArrowDown" ? 1 : -1
52      this.highlighted = Math.max(-1, Math.min(items.length - 1, this.highlighted))
53      items.forEach((a, i) => a.classList.toggle("bg-brand-soft", i === this.highlighted))
54    } else if (event.key === "Enter" && this.highlighted >= 0) {
55      event.preventDefault()
56      items[this.highlighted].click()
57    } else if (event.key === "Escape") {
58      this.close()
59    }
60  }
61
62  outside(event) {
63    if (!this.element.contains(event.target)) this.close()
64  }
65
66  close() {
67    this.listTarget.hidden = true
68    this.listTarget.innerHTML = ""
69    this.highlighted = -1
70  }
71
72  escape(s) {
73    const d = document.createElement("div")
74    d.textContent = s ?? ""
75    return d.innerHTML
76  }
77}

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.