PageSourceSearch

https://sifrovacky.cz/assets/games/controllers/popover_controller-c6fb23fd.js

js sifrovacky.cz collected 2026-10-05 02:16:27 UTC 2,703 bytes, 104 lines download raw bytes

1import { Controller } from "@hotwired/stimulus"
2
3export default class Popover extends Controller {
4  static targets = ["card", "link"]
5  static values = {
6    url: String,
7    offset: { type: Number, default: 20 }, // Offset in pixels above the cursor
8    visible: false,
9  }
10
11  connect() {
12    if (this.hasLinkTarget) {
13      this.urlValue ||= this.linkTarget.href
14
15      this.boundShow = this.show.bind(this)
16      this.boundHide = this.hide.bind(this)
17
18      this.linkTarget.addEventListener('mouseenter', this.boundShow)
19      this.linkTarget.addEventListener('mouseleave', this.boundHide)
20    }
21  }
22
23  disconnect() {
24    this.linkTarget.removeEventListener('mouseenter', this.boundShow)
25    this.linkTarget.removeEventListener('mouseleave', this.boundHide)
26  }
27
28  async show(event) {
29    if (this.pending) {
30      return
31    }
32    this.pending = true
33    this.visible = true
34    if (this.hasCardTarget) {
35      this.cardTarget.remove()
36    }
37
38    const content = await this.fetch()
39    if (!content) {
40      this.pending = false
41      return
42    }
43
44    if (this.visible) {
45      const fragment = document.createRange().createContextualFragment(content)
46      this.element.appendChild(fragment)
47
48      if (this.hasCardTarget) {
49        this.positionCardAboveCursor(event, this.cardTarget)
50        this.cardTarget.classList.add('show')
51      }
52    }
53    this.pending = false
54  }
55
56
57  hide() {
58    this.visible = false
59    if (this.hasCardTarget) {
60      this.cardTarget.classList.remove('show')
61      // Wait for the animation to complete before removing the element
62      setTimeout(() => {
63        if (this.hasCardTarget)
64          this.cardTarget.remove()
65      }, 10) // Match the transition duration from CSS
66    }
67  }
68
69  positionCardAboveCursor(event, card) {
70    // Get mouse position
71    const mouseX = event.pageX
72    const mouseY = event.pageY
73
74    // Get card dimensions
75    const cardWidth = card.offsetWidth
76    const cardHeight = card.offsetHeight
77
78    // Calculate position (centered horizontally, above the cursor)
79    const left = mouseX - (cardWidth / 2)
80    let top = mouseY - cardHeight - this.offsetValue
81
82    if (top < 0) {
83      top = mouseY + this.offsetValue
84    }
85
86    // Apply position, ensuring the card stays within viewport
87    card.style.left = `${Math.max(10, Math.min(left, window.innerWidth - cardWidth - 10))}px`
88    card.style.top = `${Math.max(10, top)}px`
89  }
90
91  async fetch() {
92    if (!this.remoteContent) {
93      const response = await fetch(this.urlValue, {
94        headers: {
95          'Accept': 'text/html',
96          'X-Content-Variant': 'card'
97        }
98      })
99      this.remoteContent = await response.text()
100    }
101
102    return this.remoteContent
103  }
104}

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.