PageSourceSearch

https://symfony.com/assets/packages-mFJjaF9.js

js symfony.com collected 2026-09-24 06:40:07 UTC 7,000 bytes, 184 lines download raw bytes

1import './styles/component-terminal.css';
2import './styles/packages.css';
3import './component-terminal.js';
4import Clipboard from 'clipboard';
5import TomSelect from 'tom-select';
6
7window.addEventListener('DOMContentLoaded', () => {
8    new Packages();
9});
10
11class Packages {
12    #searchCriteria;
13
14    constructor() {
15        this.#searchCriteria = {
16            type: 'all',
17            query: '',
18            sortField: 'daily-downloads',
19            sortDirection: 'DESC',
20        };
21
22        this.#createPackageSelector();
23        this.#createCopyToClipboardButtons();
24        this.#createSearchControls();
25    }
26
27    #createPackageSelector() {
28        const packageSelector = document.querySelector('#package-selector');
29        if (null === packageSelector) {
30            return;
31        }
32
33        new TomSelect(packageSelector, {
34            plugins: ['dropdown_input'],
35            maxOptions: null,
36            maxItems: 1,
37        });
38    }
39
40    #createCopyToClipboardButtons() {
41        const wrapperElement = document.querySelector('.package-installation');
42        if (null === wrapperElement) {
43            return;
44        }
45
46        const copyButton = wrapperElement.querySelector('button');
47        const clipboard = new Clipboard(copyButton, {
48            text: () => {
49                let texToCopy = copyButton.getAttribute('data-text-to-copy');
50
51                if (null === texToCopy) {
52                    const listOfPackages = wrapperElement.querySelector('select');
53                    const selectedPackage = listOfPackages.options[listOfPackages.selectedIndex].value;
54
55                    texToCopy = `composer require ${selectedPackage}`;
56                }
57
58                return texToCopy;
59            },
60        });
61
62        const originalCopyButtonContents = copyButton.innerHTML;
63        clipboard.on('success', (_event) => {
64            copyButton.innerHTML = 'Copied!';
65            setTimeout(() => {
66                copyButton.innerHTML = originalCopyButtonContents;
67            }, 1500);
68        });
69    }
70
71    #createSearchControls() {
72        const searchElement = document.querySelector('#package-search-control');
73        if (null === searchElement) {
74            return;
75        }
76
77        searchElement.addEventListener('input', () => {
78            this.#searchCriteria.query = searchElement.value.trim().toLowerCase();
79            this.#updatePackageList();
80        });
81
82        const filterElements = document.querySelectorAll(
83            '#package-search-type-filter input[name="package-search-type"]',
84        );
85        filterElements.forEach((filterElement) => {
86            filterElement.addEventListener('click', () => {
87                document.querySelectorAll('#package-search-type-filter label').forEach((filterLabel) => {
88                    filterLabel.classList.remove('active');
89                });
90                filterElement.closest('label').classList.add('active');
91
92                this.#searchCriteria.type = filterElement.value;
93                this.#updatePackageList();
94            });
95        });
96
97        const sortElement = document.querySelector('#package-sort-control');
98        sortElement.addEventListener('change', (event) => {
99            event.preventDefault();
100
101            const selectedOption = sortElement.options[sortElement.selectedIndex];
102            this.#searchCriteria.sortField = selectedOption.getAttribute('data-sort-field');
103            this.#searchCriteria.sortDirection = selectedOption.getAttribute('data-sort-direction');
104            this.#updatePackageList();
105        });
106    }
107
108    #updatePackageList() {
109        const otherTypes = ['polyfill', 'contract', 'pack'];
110        let numResults = 0;
111        // first, filter the list of packages based on the search criteria
112        document.querySelectorAll('#package-list .package').forEach((packageElement) => {
113            const packageType = packageElement.getAttribute('data-type');
114            const packageTypes = packageType.split(' ');
115            const isRowVisibleAfterFilters =
116                'all' === this.#searchCriteria.type ||
117                ('other' === this.#searchCriteria.type && packageTypes.some((t) => otherTypes.includes(t))) ||
118                packageTypes.includes(this.#searchCriteria.type);
119
120            const packageName = packageElement.querySelector('.name').innerText.trim().toLowerCase();
121            const packageDescription = packageElement.querySelector('.description').innerText.trim().toLowerCase();
122            const isRowVisibleAfterQuerySearch =
123                '' === this.#searchCriteria.query ||
124                this.#fuzzySearch(this.#searchCriteria.query, `${packageName} ${packageDescription}`);
125
126            const isPackageVisible = isRowVisibleAfterFilters && isRowVisibleAfterQuerySearch;
127            if (isPackageVisible) {
128                packageElement.classList.remove('d-none');
129                numResults++;
130            } else {
131                packageElement.classList.add('d-none');
132            }
133        });
134
135        // second, sort the list of packages based on the search criteria
136        const sortField = this.#searchCriteria.sortField;
137        const sortDirection = this.#searchCriteria.sortDirection;
138
139        const packageListElement = document.querySelector('#package-list');
140        const packages = Array.from(packageListElement.querySelectorAll('.package'));
141
142        packages.sort((a, b) => {
143            let valueA;
144            let valueB;
145
146            if ('name' === sortField) {
147                valueA = a.querySelector('.name').innerText.trim().toLowerCase();
148                valueB = b.querySelector('.name').innerText.trim().toLowerCase();
149            } else {
150                valueA = a.getAttribute(`data-${sortField}`);
151                valueB = b.getAttribute(`data-${sortField}`);
152            }
153
154            const comparison = Number.isNaN(valueB - valueA)
155                ? valueA.localeCompare(valueB)
156                : Number.parseFloat(valueA) - Number.parseFloat(valueB);
157
158            return 'ASC' === sortDirection ? comparison : -comparison;
159        });
160
161        const fragment = document.createDocumentFragment();
162        packages.forEach((packageElement) => fragment.appendChild(packageElement));
163
164        packageListElement.replaceChildren(fragment);
165
166        // finally, update the number of results found
167        const numSearchResultsElement = document.querySelector('.num-search-results');
168        numSearchResultsElement.innerHTML =
169            0 === numResults
170                ? `<b class="text-danger">Found ${numResults} ${1 === numResults ? 'package' : 'packages'}</b>`
171                : `Found ${numResults} ${1 === numResults ? 'package' : 'packages'}`;
172    }
173
174    #fuzzySearch(query, searchContent) {
175        let isFound = false;
176        query.split(' ').forEach((queryPart) => {
177            if (null !== searchContent && searchContent.includes(queryPart)) {
178                isFound = true;
179            }
180        });
181
182        return isFound;
183    }
184}

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.