PageSourceSearch

https://webtranslateit.com/assets/controllers/dropdown_controller-…26566db35666566c13e31004778973a046b6d713.js

js webtranslateit.com collected 2026-10-02 02:47:06 UTC 5,248 bytes, 157 lines download raw bytes

1import { Controller } from '@hotwired/stimulus'
2
3// Dropdown controller — replaces dropdown.js (jQuery) with Stimulus.
4//
5// Usage:
6//   <div data-controller="dropdown">
7//     <a href="#" data-action="click->dropdown#toggle" data-dropdown-target="trigger">Label ▾</a>
8//     <ul data-dropdown-target="menu" class="hidden">
9//       <li data-type="status" data-id="unread"><a>Unread</a></li>
10//     </ul>
11//   </div>
12//
13// Values:
14//   data-dropdown-urls-preset-value="true"  — skip client-side URL building (hrefs already set)
15//
16export default class extends Controller {
17  static targets = ['trigger', 'menu']
18  static values = { urlsPreset: { type: Boolean, default: false } }
19
20  connect () {
21    this._onClickOutside = this._onClickOutside.bind(this)
22    window.addEventListener('click', this._onClickOutside, true)
23    window.addEventListener('touchend', this._onClickOutside, true)
24  }
25
26  disconnect () {
27    window.removeEventListener('click', this._onClickOutside, true)
28    window.removeEventListener('touchend', this._onClickOutside, true)
29  }
30
31  toggle (event) {
32    event.preventDefault()
33    event.stopPropagation()
34
35    const wasOpen = !this.menuTarget.classList.contains('hidden')
36
37    // Close all other dropdowns first
38    this._closeAllDropdowns()
39
40    if (wasOpen) {
41      this.close()
42    } else {
43      this._open()
44    }
45  }
46
47  close () {
48    this.menuTarget.classList.add('hidden')
49    this.triggerTarget.classList.remove('active')
50    this._resetFilterState()
51  }
52
53  // -- Private ----------------------------------------------------------------
54
55  _open () {
56    this.menuTarget.classList.remove('hidden')
57    this.triggerTarget.classList.add('active')
58    this._highlightSelected()
59    if (!this.urlsPresetValue) this._buildUrls()
60    this._scrollToSelected()
61  }
62
63  _closeAllDropdowns () {
64    this.application.controllers
65      .filter(c => c.identifier === 'dropdown' && c !== this)
66      .forEach(c => c.close())
67  }
68
69  _onClickOutside (event) {
70    if (this.element.contains(event.target)) {
71      // Close the menu when clicking inside the menu (but not on the trigger)
72      if (this.menuTarget.contains(event.target) && !this.menuTarget.classList.contains('add-filter-menu')) {
73        this.close()
74      }
75      return
76    }
77    // Don't close if clicking inside another add-filter-menu
78    if (event.target.closest && event.target.closest('.add-filter-menu')) return
79    this.close()
80  }
81
82  // Build hrefs from data-type / data-id attributes on the current URL
83  _buildUrls () {
84    const pathname = window.location.pathname
85    const search = window.location.search.replace(/(current_id=\d*&?)/, '')
86
87    this.menuTarget.querySelectorAll('li[data-type]').forEach(li => {
88      const key = li.dataset.type
89      const value = li.dataset.id
90      const link = li.querySelector('a')
91      if (!link) return
92
93      if (key === 'source_locales') {
94        const match = pathname.match(/locales\/(.*\.\.)/)
95        if (match) link.href = pathname.replace(match[1], value + '..') + search
96        return
97      }
98
99      if (key === 'target_locales') {
100        const match = pathname.match(/(\.\..*)\/strings/)
101        if (match) link.href = pathname.replace(match[1], '..' + value) + search
102        return
103      }
104
105      const urlParams = new URLSearchParams(search)
106      if (value === '') urlParams.delete(key)
107      else urlParams.set(key, value)
108      link.href = urlParams.size === 0 ? pathname : `${pathname}?${urlParams}`
109    })
110  }
111
112  // Mark the currently-active filter item with .selected
113  _highlightSelected () {
114    const filterId = this.menuTarget.id
115    if (!filterId) return
116    const filterName = filterId.replace(/_filter$/, '')
117    const value = this._currentValue(filterName)
118
119    this.menuTarget.querySelectorAll('li.selected').forEach(li => li.classList.remove('selected'))
120    const match = this.menuTarget.querySelector(`li[data-type="${filterName}"][data-id="${value}"]`)
121    if (match) match.classList.add('selected')
122  }
123
124  _currentValue (filter) {
125    const pathname = window.location.pathname
126    const urlParams = new URLSearchParams(window.location.search.replace(/#$/, ''))
127
128    switch (filter) {
129      case 'source_locales': {
130        const m = pathname.match(/locales\/(.*)\.\./); return m ? m[1] : ''
131      }
132      case 'target_locales': {
133        const m = pathname.match(/\.\.(.*)\/(strings|terms)/); return m ? m[1] : ''
134      }
135      case 'statistics': {
136        const m = pathname.match(/statistics\/(.*)$/); return m ? m[1] : ''
137      }
138      default:
139        return urlParams.get(filter) || ''
140    }
141  }
142
143  _scrollToSelected () {
144    const selected = this.menuTarget.querySelector('li.selected')
145    if (selected) selected.scrollIntoView({ block: 'nearest' })
146  }
147
148  // Reset add-filter two-step navigation back to the filter-types panel
149  _resetFilterState () {
150    if (!this.menuTarget.classList.contains('add-filter-menu')) return
151    const types = this.menuTarget.querySelector('.filter-types')
152    const values = this.menuTarget.querySelector('.filter-values')
153    if (types) types.classList.remove('hidden')
154    if (values) values.classList.add('hidden')
155    this.menuTarget.querySelectorAll('.filter-value-panel').forEach(p => p.classList.remove('active'))
156  }
157};

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.