Поговорим о...
|
|
// Инициализация режима поиска function initSearchMode() { const selectedMode = document.querySelector('input[name="search-mode"]:checked').value; const searchBox = document.querySelector('.tree-search-box'); const searchBtn = document.getElementById('tree-search-btn'); // Удаляем все обработчики searchInput.removeEventListener('input', handleInstantSearch); if (selectedMode === 'instant') { // Мгновенный поиск searchInput.addEventListener('input', handleInstantSearch); searchBtn.classList.add('hidden'); searchBox.classList.add('no-button'); } else { // Поиск по кнопке searchBtn.classList.remove('hidden'); searchBox.classList.remove('no-button'); } } // Инициализация initSearchMode();
|
<div class="icons-gallery"> <h2>Галерея иконок</h2> {{ if .Site.Data.icons }} {{ $icons := .Site.Data.icons }} {{ $descriptions := .Site.Data.icon_descriptions | default dict }} <div class="icons-controls"> <div class="icons-stats"> Всего иконок: <strong>{{ len $icons }}</strong> </div> <div class="search-mode-container"> <label class="search-mode-label"> <input type="radio" name="search-mode" value="instant" checked> <span>Мгновенный поиск</span> </label> <label class="search-mode-label"> <input type="radio" name="search-mode" value="button"> <span>Поиск по кнопке</span> </label> </div> <div class="tree-search-box no-button"> <input type="text" id="iconsSearch" placeholder="Поиск по названию или описанию..." class="search-input" > <button id="tree-search-btn" class="hidden">Найти</button> </div> </div> <div class="icons-table-container"> <table class="icons-table" id="iconsTable"> <thead> <tr> <th>Иконка</th> <th>Название</th> <th>Описание</th> <th>Размер</th> <th>Код для вставки</th> </tr> </thead> <tbody> {{ range $name, $svg := $icons }} {{ $description := index $descriptions $name | default "" }} <tr class="icon-row" data-name="{{ $name | lower }}" data-description="{{ $description | lower }}"> <td class="icon-cell"> <div class="icon-svg-small">{{ $svg | safeHTML }}</div> </td> <td class="name-cell"> <code>{{ $name }}</code> </td> <td class="description-cell"> {{ if $description }} {{ $description }} {{ else }} <span class="no-description">—</span> {{ end }} </td> <td class="size-cell"> 16×16 px </td> <td class="code-cell"> <div class="code-snippet" onclick="copyCode(this)"> <code>{{ "{{<" }}icon "{{ $name }}"{{ ">"}}}}</code> <span class="copy-tooltip">Кликните для копирования</span> <div class="copy-success"> <div class="success-icon">✓</div> <div>Скопировано!</div> </div> </div> </td> </tr> {{ end }} </tbody> </table> </div> <div class="no-results" id="noResults" style="display: none;"> <p>Иконки не найдены. Попробуйте другой запрос.</p> </div> {{ else }} <div class="error-message"> <h3>Ошибка: данные иконок не найдены!</h3> <p>Проверьте файл data/icons.yaml</p> </div> {{ end }} </div>
<style> /* Все предыдущие стили остаются */ .icons-gallery { margin: 40px 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
.search-mode-container { display: flex; gap: 20px; margin-bottom: 15px; }
.search-mode-label { display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 14px; color: #555; }
.search-mode-label input[type="radio"] { margin: 0; }
.tree-search-box { display: flex; gap: 10px; position: relative; }
.tree-search-box.no-button .search-input { border-radius: 8px; }
.tree-search-box:not(.no-button) .search-input { border-radius: 8px 0 0 8px; border-right: none; }
#tree-search-btn { padding: 12px 20px; background: #007acc; color: white; border: none; border-radius: 0 8px 8px 0; cursor: pointer; font-size: 14px; transition: background 0.3s ease; }
#tree-search-btn:hover { background: #005a9e; }
.hidden { display: none !important; }
/* Остальные стили остаются без изменений */ .icons-controls { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; flex-wrap: wrap; gap: 15px; }
.icons-stats { background: #f8f9fa; padding: 12px 20px; border-radius: 8px; font-size: 14px; color: #555; }
.search-input { padding: 12px 16px; border: 2px solid #e1e5e9; border-radius: 8px; font-size: 14px; min-width: 250px; transition: border-color 0.3s ease; }
.search-input:focus { outline: none; border-color: #007acc; box-shadow: 0 0 0 3px rgba(0, 122, 204, 0.1); }
.icons-table-container { overflow-x: auto; border: 1px solid #e1e5e9; border-radius: 8px; background: white; }
.icons-table { width: 100%; border-collapse: collapse; }
/* ... остальные стили без изменений ... */ </style>
<script> // Функция для поиска иконок function searchIcons() { const searchTerm = document.getElementById('iconsSearch').value.toLowerCase(); const rows = document.querySelectorAll('.icon-row'); const noResults = document.getElementById('noResults'); let visibleCount = 0; rows.forEach(row => { const iconName = row.getAttribute('data-name'); const iconDescription = row.getAttribute('data-description'); if (searchTerm === '' || iconName.includes(searchTerm) || iconDescription.includes(searchTerm)) { row.style.display = ''; visibleCount++; } else { row.style.display = 'none'; } }); // Показываем/скрываем сообщение "нет результатов" if (visibleCount === 0 && searchTerm !== '') { noResults.style.display = 'block'; } else { noResults.style.display = 'none'; } }
// Обработчик мгновенного поиска function handleInstantSearch(e) { // Останавливаем всплытие чтобы не срабатывал глобальный поиск e.stopPropagation(); e.stopImmediatePropagation(); clearTimeout(this.searchTimeout); this.searchTimeout = setTimeout(() => { searchIcons(); }, 300); }
// Обработчик поиска по кнопке function handleButtonSearch(e) { e.stopPropagation(); e.stopImmediatePropagation(); e.preventDefault(); searchIcons(); }
// Инициализация режима поиска function initSearchMode() { const selectedMode = document.querySelector('input[name="search-mode"]:checked').value; const searchBox = document.querySelector('.tree-search-box'); const searchBtn = document.getElementById('tree-search-btn'); const searchInput = document.getElementById('iconsSearch');
// Удаляем все обработчики searchInput.removeEventListener('input', handleInstantSearch); searchInput.removeEventListener('keydown', handleInputKeydown); searchBtn.removeEventListener('click', handleButtonSearch);
if (selectedMode === 'instant') { // Мгновенный поиск searchInput.addEventListener('input', handleInstantSearch); searchInput.addEventListener('keydown', handleInputKeydown); searchBtn.classList.add('hidden'); searchBox.classList.add('no-button'); } else { // Поиск по кнопке searchBtn.addEventListener('click', handleButtonSearch); searchInput.addEventListener('keydown', handleInputKeydown); searchBtn.classList.remove('hidden'); searchBox.classList.remove('no-button'); } }
// Обработчик клавиш для input function handleInputKeydown(e) { // Полностью останавливаем всплытие e.stopPropagation(); e.stopImmediatePropagation(); // Enter в режиме кнопки if (e.key === 'Enter' && document.querySelector('input[name="search-mode"]:checked').value === 'button') { e.preventDefault(); searchIcons(); } // Escape очищает поиск if (e.key === 'Escape') { this.value = ''; searchIcons(); e.preventDefault(); } }
// Функция для копирования кода function copyCode(element) { const code = element.querySelector('code').textContent; // Используем modern Clipboard API navigator.clipboard.writeText(code).then(() => { // Показываем успешное сообщение const success = element.querySelector('.copy-success'); success.classList.add('show'); // Скрываем сообщение через 2 секунды setTimeout(() => { success.classList.remove('show'); }, 2000); }).catch(err => { console.error('Ошибка при копирования:', err); // Fallback для старых браузеров const textArea = document.createElement('textarea'); textArea.value = code; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); const success = element.querySelector('.copy-success'); success.classList.add('show'); setTimeout(() => { success.classList.remove('show'); }, 2000); }); }
// Инициализация после загрузки DOM document.addEventListener('DOMContentLoaded', function() { const searchInput = document.getElementById('iconsSearch'); const modeRadios = document.querySelectorAll('input[name="search-mode"]'); if (!searchInput) return; // Инициализируем поиск initSearchMode(); // Обработчики переключения режима поиска modeRadios.forEach(radio => { radio.addEventListener('change', initSearchMode); }); // Дополнительная изоляция - предотвращаем фокус на глобальный поиск searchInput.addEventListener('focus', function(e) { e.stopPropagation(); }); searchInput.addEventListener('click', function(e) { e.stopPropagation(); }); // Предотвращаем всплытие для всех событий мыши const searchContainer = document.querySelector('.tree-search-box'); if (searchContainer) { searchContainer.addEventListener('mousedown', function(e) { e.stopPropagation(); }); searchContainer.addEventListener('mouseup', function(e) { e.stopPropagation(); }); } });
// Дополнительная защита - перехватываем события до их всплытия document.addEventListener('keydown', function(e) { const searchInput = document.getElementById('iconsSearch'); if (searchInput && document.activeElement === searchInput) { e.stopImmediatePropagation(); } }, true); // Используем capture phase
document.addEventListener('keyup', function(e) { const searchInput = document.getElementById('iconsSearch'); if (searchInput && document.activeElement === searchInput) { e.stopImmediatePropagation(); } }, true);
document.addEventListener('input', function(e) { const searchInput = document.getElementById('iconsSearch'); if (searchInput && e.target === searchInput) { e.stopImmediatePropagation(); } }, true); </script>
|
<div class="icons-gallery"> <h2>Галерея иконок</h2> {{ if .Site.Data.icons }} {{ $icons := .Site.Data.icons }} {{ $descriptions := .Site.Data.icon_descriptions | default dict }} <div class="icons-controls"> <div class="icons-stats"> Всего иконок: <strong>{{ len $icons }}</strong> </div> <div class="search-mode-container"> <label class="search-mode-label"> <input type="radio" name="search-mode" value="instant" checked> <span>Мгновенный поиск</span> </label> <label class="search-mode-label"> <input type="radio" name="search-mode" value="button"> <span>Поиск по кнопке</span> </label> </div> <div class="tree-search-box no-button"> <input type="text" id="iconsSearch" placeholder="Поиск по названию или описанию..." class="search-input" > <button id="clear-search" class="clear-search-btn hidden" title="Очистить поиск"> <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor"> <path d="M1 1L13 13M13 1L1 13"/> </svg> </button> <button id="tree-search-btn" class="search-btn hidden">Найти</button> </div> </div> <div class="icons-table-container"> <table class="icons-table" id="iconsTable"> <thead> <tr> <th>Иконка</th> <th>Название</th> <th>Описание</th> <th>Размер</th> <th>Код для вставки</th> </tr> </thead> <tbody> {{ range $name, $svg := $icons }} {{ $description := index $descriptions $name | default "" }} <tr class="icon-row" data-name="{{ $name | lower }}" data-description="{{ $description | lower }}"> <td class="icon-cell"> <div class="icon-svg-small">{{ $svg | safeHTML }}</div> </td> <td class="name-cell"> <code>{{ $name }}</code> </td> <td class="description-cell"> {{ if $description }} {{ $description }} {{ else }} <span class="no-description">—</span> {{ end }} </td> <td class="size-cell"> 16×16 px </td> <td class="code-cell"> <div class="code-snippet" onclick="copyCode(this)"> <code>{{ "{{<" }}icon "{{ $name }}"{{ ">"}}}}</code> <span class="copy-tooltip">Кликните для копирования</span> <div class="copy-success"> <div class="success-icon">✓</div> <div>Скопировано!</div> </div> </div> </td> </tr> {{ end }} </tbody> </table> </div> <div class="no-results" id="noResults" style="display: none;"> <p>Иконки не найдены. Попробуйте другой запрос.</p> </div> {{ else }} <div class="error-message"> <h3>Ошибка: данные иконок не найдены!</h3> <p>Проверьте файл data/icons.yaml</p> </div> {{ end }} </div>
<style> /* Все предыдущие стили остаются */ .icons-gallery { margin: 40px 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
.search-mode-container { display: flex; gap: 20px; margin-bottom: 15px; }
.search-mode-label { display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 14px; color: #555; }
.search-mode-label input[type="radio"] { margin: 0; }
.tree-search-box { display: flex; gap: 0; position: relative; align-items: center; }
.tree-search-box.no-button .search-input { border-radius: 8px; padding-right: 35px; }
.tree-search-box:not(.no-button) .search-input { border-radius: 8px 0 0 8px; border-right: none; padding-right: 35px; }
.search-input { padding: 12px 16px; border: 2px solid #e1e5e9; font-size: 14px; min-width: 250px; transition: border-color 0.3s ease; flex: 1; }
.search-input:focus { outline: none; border-color: #007acc; box-shadow: 0 0 0 3px rgba(0, 122, 204, 0.1); }
.clear-search-btn { position: absolute; right: 45px; background: none; border: none; padding: 6px; cursor: pointer; color: #999; border-radius: 50%; transition: all 0.3s ease; }
.clear-search-btn:hover { color: #666; background: #f0f0f0; }
.clear-search-btn:not(.hidden) { display: flex; align-items: center; justify-content: center; }
.search-btn { padding: 12px 20px; background: #007acc; color: white; border: none; border-radius: 0 8px 8px 0; cursor: pointer; font-size: 14px; transition: background 0.3s ease; white-space: nowrap; }
.search-btn:hover { background: #005a9e; }
.hidden { display: none !important; }
/* Остальные стили остаются без изменений */ .icons-controls { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; flex-wrap: wrap; gap: 15px; }
.icons-stats { background: #f8f9fa; padding: 12px 20px; border-radius: 8px; font-size: 14px; color: #555; }
.icons-table-container { overflow-x: auto; border: 1px solid #e1e5e9; border-radius: 8px; background: white; }
.icons-table { width: 100%; border-collapse: collapse; }
/* ... остальные стили без изменений ... */ </style>
<script> // Функция для поиска иконок function searchIcons() { const searchTerm = document.getElementById('iconsSearch').value.toLowerCase(); const rows = document.querySelectorAll('.icon-row'); const noResults = document.getElementById('noResults'); const clearBtn = document.getElementById('clear-search'); let visibleCount = 0; rows.forEach(row => { const iconName = row.getAttribute('data-name'); const iconDescription = row.getAttribute('data-description'); if (searchTerm === '' || iconName.includes(searchTerm) || iconDescription.includes(searchTerm)) { row.style.display = ''; visibleCount++; } else { row.style.display = 'none'; } }); // Показываем/скрываем сообщение "нет результатов" if (visibleCount === 0 && searchTerm !== '') { noResults.style.display = 'block'; } else { noResults.style.display = 'none'; } // Показываем/скрываем кнопку очистки if (searchTerm !== '') { clearBtn.classList.remove('hidden'); } else { clearBtn.classList.add('hidden'); } }
// Функция очистки поиска function clearSearch() { const searchInput = document.getElementById('iconsSearch'); searchInput.value = ''; searchInput.focus(); searchIcons(); }
// Обработчик мгновенного поиска function handleInstantSearch(e) { // Останавливаем всплытие чтобы не срабатывал глобальный поиск e.stopPropagation(); e.stopImmediatePropagation(); clearTimeout(this.searchTimeout); this.searchTimeout = setTimeout(() => { searchIcons(); }, 300); }
// Обработчик поиска по кнопке function handleButtonSearch(e) { e.stopPropagation(); e.stopImmediatePropagation(); e.preventDefault(); searchIcons(); }
// Обработчик Enter для поиска function handleEnterSearch(e) { if (e.key === 'Enter') { e.stopPropagation(); e.stopImmediatePropagation(); e.preventDefault(); searchIcons(); } // Escape очищает поиск if (e.key === 'Escape') { clearSearch(); e.preventDefault(); } }
// Инициализация режима поиска function initSearchMode() { const selectedMode = document.querySelector('input[name="search-mode"]:checked').value; const searchBox = document.querySelector('.tree-search-box'); const searchBtn = document.getElementById('tree-search-btn'); const searchInput = document.getElementById('iconsSearch'); const clearBtn = document.getElementById('clear-search');
// Удаляем все обработчики searchInput.removeEventListener('input', handleInstantSearch); searchInput.removeEventListener('keydown', handleEnterSearch); searchBtn.removeEventListener('click', handleButtonSearch); clearBtn.removeEventListener('click', clearSearch);
// Добавляем общие обработчики searchInput.addEventListener('keydown', handleEnterSearch); clearBtn.addEventListener('click', clearSearch);
if (selectedMode === 'instant') { // Мгновенный поиск searchInput.addEventListener('input', handleInstantSearch); searchBtn.classList.add('hidden'); searchBox.classList.add('no-button'); } else { // Поиск по кнопке searchBtn.addEventListener('click', handleButtonSearch); searchBtn.classList.remove('hidden'); searchBox.classList.remove('no-button'); } // Обновляем отображение кнопки очистки searchIcons(); }
// Функция для копирования кода function copyCode(element) { const code = element.querySelector('code').textContent; // Используем modern Clipboard API navigator.clipboard.writeText(code).then(() => { // Показываем успешное сообщение const success = element.querySelector('.copy-success'); success.classList.add('show'); // Скрываем сообщение через 2 секунды setTimeout(() => { success.classList.remove('show'); }, 2000); }).catch(err => { console.error('Ошибка при копирования:', err); // Fallback для старых браузеров const textArea = document.createElement('textarea'); textArea.value = code; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); const success = element.querySelector('.copy-success'); success.classList.add('show'); setTimeout(() => { success.classList.remove('show'); }, 2000); }); }
// Инициализация после загрузки DOM document.addEventListener('DOMContentLoaded', function() { const searchInput = document.getElementById('iconsSearch'); const modeRadios = document.querySelectorAll('input[name="search-mode"]'); if (!searchInput) return; // Инициализируем поиск initSearchMode(); // Обработчики переключения режима поиска modeRadios.forEach(radio => { radio.addEventListener('change', initSearchMode); }); // Дополнительная изоляция - предотвращаем фокус на глобальный поиск const stopPropagation = (e) => e.stopPropagation(); searchInput.addEventListener('focus', stopPropagation); searchInput.addEventListener('click', stopPropagation); searchInput.addEventListener('mousedown', stopPropagation); const searchContainer = document.querySelector('.tree-search-box'); if (searchContainer) { searchContainer.addEventListener('mousedown', stopPropagation); searchContainer.addEventListener('mouseup', stopPropagation); } });
// Дополнительная защита - перехватываем события до их всплытия document.addEventListener('keydown', function(e) { const searchInput = document.getElementById('iconsSearch'); if (searchInput && (document.activeElement === searchInput || e.target === searchInput)) { e.stopImmediatePropagation(); } }, true);
document.addEventListener('keyup', function(e) { const searchInput = document.getElementById('iconsSearch'); if (searchInput && (document.activeElement === searchInput || e.target === searchInput)) { e.stopImmediatePropagation(); } }, true);
document.addEventListener('input', function(e) { const searchInput = document.getElementById('iconsSearch'); if (searchInput && e.target === searchInput) { e.stopImmediatePropagation(); } }, true); </script>
|
<div class="icons-gallery"> <h2>Галерея иконок</h2> {{ if .Site.Data.icons }} {{ $icons := .Site.Data.icons }} {{ $descriptions := .Site.Data.icon_descriptions | default dict }} <div class="icons-controls"> <div class="icons-stats"> Всего иконок: <strong>{{ len $icons }}</strong> </div> <div class="search-container"> <div class="search-mode-container"> <label class="search-mode-label"> <input type="radio" name="search-mode" value="instant" checked> <span>Мгновенный поиск</span> </label> <label class="search-mode-label"> <input type="radio" name="search-mode" value="button"> <span>Поиск по кнопке</span> </label> </div> <div class="search-box"> <input type="text" id="iconsSearch" placeholder="Поиск по названию или описанию..." class="search-input" > <button id="clearSearch" class="clear-btn" title="Очистить поиск"> <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor"> <path d="M1 1L13 13M13 1L1 13"/> </svg> </button> <button id="searchBtn" class="search-btn">Найти</button> </div> </div> </div> <div class="icons-table-container"> <table class="icons-table" id="iconsTable"> <thead> <tr> <th>Иконка</th> <th>Название</th> <th>Описание</th> <th>Размер</th> <th>Код для вставки</th> </tr> </thead> <tbody> {{ range $name, $svg := $icons }} {{ $description := index $descriptions $name | default "" }} <tr class="icon-row" data-name="{{ $name | lower }}" data-description="{{ $description | lower }}"> <td class="icon-cell"> <div class="icon-svg-small">{{ $svg | safeHTML }}</div> </td> <td class="name-cell"> <code>{{ $name }}</code> </td> <td class="description-cell"> {{ if $description }} {{ $description }} {{ else }} <span class="no-description">—</span> {{ end }} </td> <td class="size-cell"> 16×16 px </td> <td class="code-cell"> <div class="code-snippet" onclick="copyCode(this)"> <code>{{ "{{<" }}icon "{{ $name }}"{{ ">"}}}}</code> <span class="copy-tooltip">Кликните для копирования</span> <div class="copy-success"> <div class="success-icon">✓</div> <div>Скопировано!</div> </div> </div> </td> </tr> {{ end }} </tbody> </table> </div> <div class="no-results" id="noResults" style="display: none;"> <p>Иконки не найдены. Попробуйте другой запрос.</p> </div> {{ else }} <div class="error-message"> <h3>Ошибка: данные иконок не найдены!</h3> <p>Проверьте файл data/icons.yaml</p> </div> {{ end }} </div>
<style> .icons-gallery { margin: 40px 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
.icons-controls { display: flex; justify-content: space-between; align-items: start; margin-bottom: 20px; flex-wrap: wrap; gap: 20px; }
.icons-stats { background: #f8f9fa; padding: 12px 20px; border-radius: 8px; font-size: 14px; color: #555; min-width: 150px; }
.search-container { display: flex; flex-direction: column; gap: 15px; min-width: 300px; }
.search-mode-container { display: flex; gap: 20px; }
.search-mode-label { display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 14px; color: #555; }
.search-mode-label input[type="radio"] { margin: 0; }
.search-box { display: flex; position: relative; width: 100%; }
.search-input { padding: 12px 40px 12px 16px; border: 2px solid #e1e5e9; border-radius: 8px; font-size: 14px; width: 100%; transition: border-color 0.3s ease; background: white; }
.search-input:focus { outline: none; border-color: #007acc; box-shadow: 0 0 0 3px rgba(0, 122, 204, 0.1); }
.search-input:focus + .clear-btn { opacity: 1; }
.clear-btn { position: absolute; right: 50px; top: 50%; transform: translateY(-50%); background: none; border: none; padding: 4px; cursor: pointer; color: #999; border-radius: 50%; transition: all 0.3s ease; opacity: 0; display: flex; align-items: center; justify-content: center; }
.clear-btn:hover { color: #666; background: #f0f0f0; }
.search-input:not(:placeholder-shown) + .clear-btn { opacity: 1; }
.search-btn { padding: 12px 20px; background: #007acc; color: white; border: none; border-radius: 0 8px 8px 0; cursor: pointer; font-size: 14px; transition: background 0.3s ease; white-space: nowrap; margin-left: -2px; }
.search-btn:hover { background: #005a9e; }
.icons-table-container { overflow-x: auto; border: 1px solid #e1e5e9; border-radius: 8px; background: white; margin-top: 10px; }
.icons-table { width: 100%; border-collapse: collapse; }
.icons-table th { background: #f8f9fa; padding: 16px; text-align: left; font-weight: 600; color: #555; border-bottom: 2px solid #e1e5e9; }
.icons-table td { padding: 12px 16px; border-bottom: 1px solid #f0f0f0; }
.icon-row:hover { background: #fafafa; }
.icon-cell { text-align: center; width: 60px; }
.icon-svg-small { width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; margin: 0 auto; }
.icon-svg-small svg { width: 16px; height: 16px; min-width: 16px; min-height: 16px; }
.name-cell { font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 13px; color: #333; width: 150px; }
.name-cell code { background: #f5f5f5; padding: 4px 8px; border-radius: 4px; border: 1px solid #eee; font-size: 12px; }
.description-cell { color: #666; font-size: 14px; line-height: 1.4; max-width: 300px; }
.no-description { color: #999; font-style: italic; }
.size-cell { color: #888; font-size: 12px; text-align: center; width: 80px; }
.code-cell { width: 200px; }
.code-snippet { position: relative; background: #f8f9fa; padding: 10px 14px; border-radius: 6px; border: 1px solid #e9ecef; cursor: pointer; transition: all 0.3s ease; font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 13px; min-height: 40px; display: flex; align-items: center; }
.code-snippet:hover { background: #e3f2fd; border-color: #2196f3; }
.code-snippet code { background: none; border: none; padding: 0; color: #1976d2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block; flex: 1; }
.copy-tooltip { position: absolute; top: -35px; left: 50%; transform: translateX(-50%); background: #333; color: white; padding: 6px 12px; border-radius: 4px; font-size: 12px; opacity: 0; transition: opacity 0.3s ease; pointer-events: none; white-space: nowrap; z-index: 1000; }
.code-snippet:hover .copy-tooltip { opacity: 1; }
.copy-success { position: absolute; top: -60px; left: 50%; transform: translateX(-50%); background: #4caf50; color: white; padding: 10px 15px; border-radius: 6px; font-size: 12px; opacity: 0; transition: opacity 0.3s ease; pointer-events: none; z-index: 1000; text-align: center; display: flex; flex-direction: column; align-items: center; gap: 4px; }
.copy-success.show { opacity: 1; }
.success-icon { font-size: 16px; font-weight: bold; }
.no-results { text-align: center; padding: 40px; color: #888; font-style: italic; display: none; }
.error-message { color: #d32f2f; padding: 20px; border: 2px solid #ffcdd2; border-radius: 8px; background: #ffebee; }
.hidden { display: none !important; }
/* Адаптивность */ @media (max-width: 1024px) { .icons-controls { flex-direction: column; align-items: stretch; } .search-container { width: 100%; } .description-cell { display: none; } .code-cell { width: 180px; } }
@media (max-width: 768px) { .search-mode-container { flex-direction: column; gap: 10px; } .search-box { flex-direction: column; gap: 10px; } .search-input { border-radius: 8px; padding-right: 16px; } .clear-btn { right: 10px; } .search-btn { border-radius: 8px; margin-left: 0; } .icons-table th, .icons-table td { padding: 10px 12px; } .code-cell { display: none; } .name-cell { width: 120px; } } </style>
<script> // Глобальные переменные let searchTimeout = null;
// Функция для поиска иконок function searchIcons() { const searchTerm = document.getElementById('iconsSearch').value.toLowerCase(); const rows = document.querySelectorAll('.icon-row'); const noResults = document.getElementById('noResults'); const clearBtn = document.getElementById('clearSearch'); let visibleCount = 0; rows.forEach(row => { const iconName = row.getAttribute('data-name'); const iconDescription = row.getAttribute('data-description'); if (searchTerm === '' || iconName.includes(searchTerm) || iconDescription.includes(searchTerm)) { row.style.display = ''; visibleCount++; } else { row.style.display = 'none'; } }); // Показываем/скрываем сообщение "нет результатов" if (visibleCount === 0 && searchTerm !== '') { noResults.style.display = 'block'; } else { noResults.style.display = 'none'; } // Показываем/скрываем кнопку очистки if (searchTerm !== '') { clearBtn.style.display = 'flex'; } else { clearBtn.style.display = 'none'; } }
// Функция очистки поиска function clearSearch() { const searchInput = document.getElementById('iconsSearch'); searchInput.value = ''; searchInput.focus(); searchIcons(); }
// Обработчик мгновенного поиска function handleInstantSearch(e) { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { searchIcons(); }, 300); }
// Обработчик поиска по кнопке function handleButtonSearch() { searchIcons(); }
// Обработчик клавиш function handleKeyDown(e) { // Enter запускает поиск if (e.key === 'Enter') { e.preventDefault(); searchIcons(); } // Escape очищает поиск if (e.key === 'Escape') { e.preventDefault(); clearSearch(); } }
// Инициализация режима поиска function initSearchMode() { const selectedMode = document.querySelector('input[name="search-mode"]:checked').value; const searchInput = document.getElementById('iconsSearch'); const searchBtn = document.getElementById('searchBtn'); const clearBtn = document.getElementById('clearSearch');
// Удаляем все обработчики searchInput.removeEventListener('input', handleInstantSearch); searchInput.removeEventListener('keydown', handleKeyDown); searchBtn.removeEventListener('click', handleButtonSearch); clearBtn.removeEventListener('click', clearSearch);
// Добавляем общие обработчики searchInput.addEventListener('keydown', handleKeyDown); clearBtn.addEventListener('click', clearSearch);
if (selectedMode === 'instant') { // Мгновенный поиск searchInput.addEventListener('input', handleInstantSearch); searchBtn.style.display = 'none'; searchInput.style.borderRadius = '8px'; searchInput.style.paddingRight = '40px'; } else { // Поиск по кнопке searchBtn.addEventListener('click', handleButtonSearch); searchBtn.style.display = 'block'; searchInput.style.borderRadius = '8px 0 0 8px'; searchInput.style.paddingRight = '40px'; } // Обновляем отображение searchIcons(); }
// Функция для копирования кода function copyCode(element) { const code = element.querySelector('code').textContent; navigator.clipboard.writeText(code).then(() => { const success = element.querySelector('.copy-success'); success.classList.add('show'); setTimeout(() => { success.classList.remove('show'); }, 2000); }).catch(err => { console.error('Ошибка при копировании:', err); // Fallback для старых браузеров const textArea = document.createElement('textarea'); textArea.value = code; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); const success = element.querySelector('.copy-success'); success.classList.add('show'); setTimeout(() => { success.classList.remove('show'); }, 2000); }); }
// Инициализация после загрузки DOM document.addEventListener('DOMContentLoaded', function() { // Инициализируем поиск initSearchMode(); // Обработчики переключения режима поиска const modeRadios = document.querySelectorAll('input[name="search-mode"]'); modeRadios.forEach(radio => { radio.addEventListener('change', initSearchMode); }); // Изоляция событий const searchInput = document.getElementById('iconsSearch'); if (searchInput) { const stopEvents = (e) => { e.stopPropagation(); e.stopImmediatePropagation(); }; searchInput.addEventListener('keydown', stopEvents, true); searchInput.addEventListener('keyup', stopEvents, true); searchInput.addEventListener('input', stopEvents, true); searchInput.addEventListener('focus', stopEvents, true); searchInput.addEventListener('click', stopEvents, true); } });
// Глобальная защита от всплытия document.addEventListener('keydown', function(e) { const searchInput = document.getElementById('iconsSearch'); if (searchInput && document.activeElement === searchInput) { e.stopImmediatePropagation(); } }, true); </script>
|
<div class="icons-gallery"> <h2>Галерея иконок</h2> {{ if .Site.Data.icons }} {{ $icons := .Site.Data.icons }} {{ $descriptions := .Site.Data.icon_descriptions | default dict }} <div class="icons-controls"> <div class="icons-stats"> Всего иконок: <strong>{{ len $icons }}</strong> </div> <div class="search-filters"> <label class="filter-checkbox"> <input type="checkbox" id="showWithDescription"> <span>Только с описанием</span> </label> </div> <div class="search-container"> <div class="search-mode-container"> <label class="search-mode-label"> <input type="radio" name="search-mode" value="instant" checked> <span>Мгновенный поиск</span> </label> <label class="search-mode-label"> <input type="radio" name="search-mode" value="button"> <span>Поиск по кнопке</span> </label> </div> <div class="search-box instant-mode"> <input type="text" id="iconsSearch" placeholder="Поиск по названию или описанию..." class="search-input" > <button id="clearSearch" class="clear-btn" title="Очистить поиск"> <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor"> <path d="M1 1L13 13M13 1L1 13"/> </svg> </button> <button id="searchBtn" class="search-btn">Найти</button> </div> </div> </div> <div class="icons-table-container"> <table class="icons-table" id="iconsTable"> <thead> <tr> <th>Иконка</th> <th>Название</th> <th>Описание</th> <th>Размер</th> <th>Код для вставки</th> </tr> </thead> <tbody> {{ range $name, $svg := $icons }} {{ $description := index $descriptions $name | default "" }} <tr class="icon-row" data-name="{{ $name | lower }}" data-description="{{ $description | lower }}" data-has-description="{{ if $description }}true{{ else }}false{{ end }}"> <td class="icon-cell"> <div class="icon-svg-small">{{ $svg | safeHTML }}</div> </td> <td class="name-cell"> <code>{{ $name }}</code> </td> <td class="description-cell"> {{ if $description }} {{ $description }} {{ else }} <span class="no-description">—</span> {{ end }} </td> <td class="size-cell"> 16×16 px </td> <td class="code-cell"> <div class="code-snippet" onclick="copyCode(this)"> <code>{{ "{{<" }}icon "{{ $name }}"{{ ">"}}}}</code> <span class="copy-tooltip">Кликните для копирования</span> <div class="copy-success"> <div class="success-icon">✓</div> <div>Скопировано!</div> </div> </div> </td> </tr> {{ end }} </tbody> </table> </div> <div class="no-results" id="noResults"> <p>Иконки не найдены. Попробуйте другой запрос.</p> </div> {{ else }} <div class="error-message"> <h3>Ошибка: данные иконок не найдены!</h3> <p>Проверьте файл data/icons.yaml</p> </div> {{ end }} </div>
<style> .icons-gallery { margin: 40px 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
.icons-controls { display: grid; grid-template-columns: auto 1fr; gap: 20px; margin-bottom: 20px; align-items: start; }
.icons-stats { background: #f8f9fa; padding: 12px 20px; border-radius: 8px; font-size: 14px; color: #555; white-space: nowrap; }
.search-filters { grid-column: 1; grid-row: 2; }
.filter-checkbox { display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 14px; color: #555; padding: 8px 12px; border-radius: 6px; transition: background 0.3s ease; }
.filter-checkbox:hover { background: #f0f0f0; }
.filter-checkbox input[type="checkbox"] { margin: 0; }
.search-container { grid-column: 2; grid-row: 1 / span 2; display: flex; flex-direction: column; gap: 12px; min-width: 300px; }
.search-mode-container { display: flex; gap: 20px; }
.search-mode-label { display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 14px; color: #555; }
.search-mode-label input[type="radio"] { margin: 0; }
.search-box { display: flex; position: relative; width: 100%; align-items: center; }
.search-input { padding: 12px 40px 12px 16px; border: 2px solid #e1e5e9; border-radius: 8px; font-size: 14px; width: 100%; transition: border-color 0.3s ease; background: white; flex: 1; }
.search-input:focus { outline: none; border-color: #007acc; box-shadow: 0 0 0 3px rgba(0, 122, 204, 0.1); }
.clear-btn { position: absolute; right: 110px; top: 50%; transform: translateY(-50%); background: none; border: none; padding: 6px; cursor: pointer; color: #999; border-radius: 50%; transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; opacity: 0; pointer-events: none; }
.clear-btn.visible { opacity: 1; pointer-events: auto; }
.clear-btn:hover { color: #666; background: #f0f0f0; }
.search-btn { padding: 12px 20px; background: #007acc; color: white; border: 2px solid #007acc; border-radius: 8px; cursor: pointer; font-size: 14px; transition: all 0.3s ease; white-space: nowrap; margin-left: 10px; flex-shrink: 0; }
.search-btn:hover { background: #005a9e; border-color: #005a9e; }
/* Режимы поиска */ .search-box.instant-mode .search-btn { display: none; }
.search-box.button-mode .search-btn { display: block; }
.search-box.button-mode .search-input { border-radius: 8px 0 0 8px; border-right: none; padding-right: 40px; }
.search-box.button-mode .clear-btn { right: 110px; }
.search-box.instant-mode .search-input { border-radius: 8px; padding-right: 40px; }
.search-box.instant-mode .clear-btn { right: 10px; }
.icons-table-container { overflow-x: auto; border: 1px solid #e1e5e9; border-radius: 8px; background: white; margin-top: 10px; }
.icons-table { width: 100%; border-collapse: collapse; }
.icons-table th { background: #f8f9fa; padding: 16px; text-align: left; font-weight: 600; color: #555; border-bottom: 2px solid #e1e5e9; }
.icons-table td { padding: 12px 16px; border-bottom: 1px solid #f0f0f0; }
.icon-row { transition: opacity 0.3s ease; }
.icon-row.hidden { display: none; }
.icon-row:hover { background: #fafafa; }
.icon-cell { text-align: center; width: 60px; }
.icon-svg-small { width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; margin: 0 auto; }
.icon-svg-small svg { width: 16px; height: 16px; min-width: 16px; min-height: 16px; }
.name-cell { font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 13px; color: #333; width: 150px; }
.name-cell code { background: #f5f5f5; padding: 4px 8px; border-radius: 4px; border: 1px solid #eee; font-size: 12px; }
.description-cell { color: #666; font-size: 14px; line-height: 1.4; max-width: 300px; }
.no-description { color: #999; font-style: italic; }
.size-cell { color: #888; font-size: 12px; text-align: center; width: 80px; }
.code-cell { width: 200px; }
.code-snippet { position: relative; background: #f8f9fa; padding: 10px 14px; border-radius: 6px; border: 1px solid #e9ecef; cursor: pointer; transition: all 0.3s ease; font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 13px; min-height: 40px; display: flex; align-items: center; }
.code-snippet:hover { background: #e3f2fd; border-color: #2196f3; }
.code-snippet code { background: none; border: none; padding: 0; color: #1976d2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block; flex: 1; }
.copy-tooltip { position: absolute; top: -35px; left: 50%; transform: translateX(-50%); background: #333; color: white; padding: 6px 12px; border-radius: 4px; font-size: 12px; opacity: 0; transition: opacity 0.3s ease; pointer-events: none; white-space: nowrap; z-index: 1000; }
.code-snippet:hover .copy-tooltip { opacity: 1; }
.copy-success { position: absolute; top: -60px; left: 50%; transform: translateX(-50%); background: #4caf50; color: white; padding: 10px 15px; border-radius: 6px; font-size: 12px; opacity: 0; transition: opacity 0.3s ease; pointer-events: none; z-index: 1000; text-align: center; display: flex; flex-direction: column; align-items: center; gap: 4px; }
.copy-success.show { opacity: 1; }
.success-icon { font-size: 16px; font-weight: bold; }
.no-results { text-align: center; padding: 40px; color: #888; font-style: italic; display: none; }
.error-message { color: #d32f2f; padding: 20px; border: 2px solid #ffcdd2; border-radius: 8px; background: #ffebee; }
/* Адаптивность */ @media (max-width: 1024px) { .icons-controls { grid-template-columns: 1fr; gap: 15px; } .search-container { grid-column: 1; grid-row: 3; } .description-cell { display: none; } .code-cell { width: 180px; } }
@media (max-width: 768px) { .search-mode-container { flex-direction: column; gap: 10px; } .search-box { flex-direction: column; gap: 10px; } .search-input { border-radius: 8px !important; padding-right: 16px; } .clear-btn { right: 10px !important; top: 50%; } .search-btn { margin-left: 0; width: 100%; } .icons-table th, .icons-table td { padding: 10px 12px; } .code-cell { display: none; } .name-cell { width: 120px; } .search-box.button-mode .search-input { border-right: 2px solid #e1e5e9; } .search-box.button-mode .clear-btn { right: 10px !important; } } </style>
<script> // Глобальные переменные let searchTimeout = null; let currentSearchMode = 'instant'; let showOnlyWithDescription = false;
// Основная функция поиска и фильтрации function performSearch() { const searchTerm = document.getElementById('iconsSearch').value.toLowerCase(); const rows = document.querySelectorAll('.icon-row'); const noResults = document.getElementById('noResults'); const clearBtn = document.getElementById('clearSearch'); let visibleCount = 0; rows.forEach(row => { const iconName = row.getAttribute('data-name'); const iconDescription = row.getAttribute('data-description'); const hasDescription = row.getAttribute('data-has-description') === 'true'; // Проверяем фильтр по описанию const descriptionFilter = !showOnlyWithDescription || hasDescription; // Проверяем поисковый запрос const searchFilter = searchTerm === '' || iconName.includes(searchTerm) || iconDescription.includes(searchTerm); // Показываем/скрываем строку if (descriptionFilter && searchFilter) { row.classList.remove('hidden'); visibleCount++; } else { row.classList.add('hidden'); } }); // Показываем/скрываем сообщение "нет результатов" noResults.style.display = visibleCount === 0 ? 'block' : 'none'; // Показываем/скрываем кнопку очистки clearBtn.classList.toggle('visible', searchTerm !== ''); }
// Функция очистки поиска function clearSearch() { const searchInput = document.getElementById('iconsSearch'); searchInput.value = ''; searchInput.focus(); performSearch(); }
// Обработчик мгновенного поиска function handleInstantSearch() { clearTimeout(searchTimeout); searchTimeout = setTimeout(performSearch, 300); }
// Обработчик поиска по кнопке function handleButtonSearch() { performSearch(); }
// Обработчик клавиш function handleKeyDown(e) { if (e.key === 'Enter') { e.preventDefault(); performSearch(); } if (e.key === 'Escape') { e.preventDefault(); clearSearch(); } }
// Переключение режима поиска function initSearchMode() { const searchMode = document.querySelector('input[name="search-mode"]:checked').value; const searchBox = document.querySelector('.search-box'); const searchInput = document.getElementById('iconsSearch'); const searchBtn = document.getElementById('searchBtn'); currentSearchMode = searchMode; // Обновляем классы для стилей searchBox.className = 'search-box ' + (searchMode === 'instant' ? 'instant-mode' : 'button-mode'); // Обновляем обработчики searchInput.removeEventListener('input', handleInstantSearch); searchBtn.removeEventListener('click', handleButtonSearch); if (searchMode === 'instant') { searchInput.addEventListener('input', handleInstantSearch); } else { searchBtn.addEventListener('click', handleButtonSearch); } }
// Фильтр по описанию function toggleDescriptionFilter() { showOnlyWithDescription = document.getElementById('showWithDescription').checked; performSearch(); }
// Функция для копирования кода function copyCode(element) { const code = element.querySelector('code').textContent; navigator.clipboard.writeText(code).then(() => { const success = element.querySelector('.copy-success'); success.classList.add('show'); setTimeout(() => { success.classList.remove('show'); }, 2000); }).catch(err => { // Fallback const textArea = document.createElement('textarea'); textArea.value = code; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); const success = element.querySelector('.copy-success'); success.classList.add('show'); setTimeout(() => { success.classList.remove('show'); }, 2000); }); }
// Инициализация document.addEventListener('DOMContentLoaded', function() { // Инициализируем элементы const searchInput = document.getElementById('iconsSearch'); const clearBtn = document.getElementById('clearSearch'); const modeRadios = document.querySelectorAll('input[name="search-mode"]'); const descriptionFilter = document.getElementById('showWithDescription'); // Основные обработчики searchInput.addEventListener('keydown', handleKeyDown); clearBtn.addEventListener('click', clearSearch); descriptionFilter.addEventListener('change', toggleDescriptionFilter); // Обработчики переключения режима modeRadios.forEach(radio => { radio.addEventListener('change', initSearchMode); }); // Инициализация initSearchMode(); performSearch(); // Изоляция событий const stopEvents = (e) => { if (e.target === searchInput) { e.stopPropagation(); } }; searchInput.addEventListener('keydown', stopEvents, true); searchInput.addEventListener('keyup', stopEvents, true); searchInput.addEventListener('input', stopEvents, true); }); </script>
|
<script> document.addEventListener('DOMContentLoaded', function() { // Элементы управления const searchInput = document.getElementById('rockSearchField'); const searchBtn = document.getElementById('rockSearchButton'); const clearBtn = document.getElementById('rockResetButton'); const modeRadios = document.querySelectorAll('input[name="search-mode"]');
let currentSearchTerm = ''; let currentMode = 'button'; let searchTimeout; let originalContents = new Map();
// Стили для подсветки const style = document.createElement('style'); style.textContent = ` .rock-search-match { background-color: #889cf8; font-weight: bold; border-radius: 3px; padding: 0 2px; } .tab-with-results { position: relative; background-color: #f0f7ff !important; } .tab-with-results::after { content: ''; position: absolute; top: 5px; right: 5px; width: 8px; height: 8px; background-color: #ff5722; border-radius: 50%; } .hidden-row { display: none !important; } `; document.head.appendChild(style);
// Сохраняем оригинальное содержимое function saveOriginalContents() { document.querySelectorAll('td, th').forEach(cell => { originalContents.set(cell, cell.innerHTML); }); }
// Восстанавливаем оригинальное содержимое function restoreOriginalContents() { originalContents.forEach((content, cell) => { cell.innerHTML = content; }); }
// Функция сброса поиска function resetSearch() { searchInput.value = ''; currentSearchTerm = ''; restoreOriginalContents();
document.querySelectorAll('.tab-with-results, .hidden-row').forEach(el => { el.classList.remove('tab-with-results', 'hidden-row'); });
document.querySelectorAll('tr, details').forEach(el => { el.style.display = ''; });
document.querySelectorAll('details').forEach(section => { section.open = false; });
const tabs = document.querySelectorAll('.tab-content'); if (tabs.length) { tabs.forEach(tab => tab.style.display = 'none'); tabs[0].style.display = ''; } }
// Улучшенная функция выделения совпадений function highlightMatches(text, searchTerm) { if (!searchTerm) return text; try { const regex = new RegExp(`(${searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi'); return text.replace(regex, '<span class="rock-search-match">$1</span>'); } catch (e) { return text; } }
// Основная функция поиска (полностью переработанная) function performSearch() { const searchTerm = searchInput.value.trim().toLowerCase(); if (searchTerm === currentSearchTerm) return; currentSearchTerm = searchTerm;
if (searchTerm === '') { resetSearch(); return; }
restoreOriginalContents(); let foundCount = 0;
// Ищем во ВСЕХ строках таблиц (включая заголовки) document.querySelectorAll('table').forEach(table => { const allRows = table.querySelectorAll('tr'); let tableHasMatches = false;
allRows.forEach(row => { const cells = row.querySelectorAll('td, th'); let rowHasMatches = false;
cells.forEach(cell => { const originalHtml = originalContents.get(cell) || cell.innerHTML; const textContent = cell.textContent.toLowerCase();
if (textContent.includes(searchTerm)) { cell.innerHTML = highlightMatches(originalHtml, searchTerm); foundCount++; rowHasMatches = true; tableHasMatches = true; } });
// Не скрываем заголовки таблиц (первые строки) if (row !== table.querySelector('tr:first-child')) { row.style.display = rowHasMatches ? '' : 'none'; } });
// Показываем/скрываем родительские элементы const parentSection = table.closest('details'); if (parentSection) { parentSection.style.display = tableHasMatches ? '' : 'none'; if (tableHasMatches) parentSection.open = true; } });
// Обработка вкладок document.querySelectorAll('.tab-content').forEach(tab => { const tabTables = tab.querySelectorAll('table'); let tabHasMatches = false;
tabTables.forEach(table => { const hasMatches = Array.from(table.querySelectorAll('td, th')).some(cell => cell.textContent.toLowerCase().includes(searchTerm) );
if (hasMatches) { tabHasMatches = true; table.querySelectorAll('tr').forEach(row => { row.style.display = ''; }); } });
if (tabHasMatches) { tab.style.display = ''; const tabId = tab.id; if (tabId) { const tabHeader = document.querySelector(`[data-tab="${tabId}"]`); if (tabHeader) tabHeader.classList.add('tab-with-results'); } } else { tab.style.display = 'none'; } }); }
// Инициализация режима поиска function initSearchMode() { currentMode = document.querySelector('input[name="search-mode"]:checked').value;
searchInput.removeEventListener('input', handleInstantSearch); searchInput.removeEventListener('input', handleDelayedSearch);
if (currentMode === 'instant') { searchInput.addEventListener('input', handleInstantSearch); searchBtn.classList.add('hidden'); document.querySelector('.tree-search-box').classList.add('no-button'); } else { searchInput.addEventListener('input', handleDelayedSearch); searchBtn.classList.remove('hidden'); document.querySelector('.tree-search-box').classList.remove('no-button'); } }
// Обработчики событий function handleInstantSearch() { clearTimeout(searchTimeout); performSearch(); }
function handleDelayedSearch() { clearTimeout(searchTimeout); searchTimeout = setTimeout(() => { if (searchInput.value.trim() === '') { resetSearch(); } }, 300); }
// Инициализация saveOriginalContents(); initSearchMode(); searchBtn.addEventListener('click', performSearch); searchInput.addEventListener('keypress', (e) => e.key === 'Enter' && performSearch()); clearBtn.addEventListener('click', resetSearch); modeRadios.forEach(radio => radio.addEventListener('change', initSearchMode)); }); </script>
<style> .tree-search-panel { margin: 20px 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }
.tree-search-box { position: relative; display: flex; margin-bottom: 8px; height: 36px; /* Фиксированная высота */ align-items: center; /* Выравнивание по центру */ }
.tree-search-input { flex: 1; height: 100%; padding: 0 30px 0 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; box-sizing: border-box; transition: border-color 0.2s; }
.tree-search-input:focus { border-color: #2188ff; outline: none; }
.tree-search-btn { height: 36px; /* Такая же высота как у поля ввода */ margin-left: 8px; padding: 0 12px; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; flex-shrink: 0; /* Не сжимается при скрытии */ transition: opacity 0.2s, width 0.2s, margin 0.2s; }
.tree-search-btn:hover { background: #68aeff; }
.tree-search-btn.hidden { opacity: 0; width: 0; margin-left: 0; padding: 0; overflow: hidden; }
.tree-search-clear { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); background: none; border: none; cursor: pointer; font-size: 18px; color: #999; transition: all 0.2s; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; }
.tree-search-clear:hover { color: #666; }
.tree-search-box:not(.no-button) .tree-search-clear { right: 90px; }
.search-modes { display: flex; gap: 15px; font-size: 13px; color: #666; padding: 5px 0; }
.search-modes label { display: flex; align-items: center; gap: 5px; cursor: pointer; user-select: none; }
.search-modes input[type="radio"] { margin: 0; } </style>
|
<div class="icons-gallery"> <h2>Галерея иконок</h2> {{ if .Site.Data.icons }} {{ $icons := .Site.Data.icons }} {{ $descriptions := .Site.Data.icon_descriptions | default dict }} <div class="icons-controls"> <div class="icons-stats"> Всего иконок: <strong>{{ len $icons }}</strong> </div> <div class="search-filters"> <label class="filter-checkbox"> <input type="checkbox" id="showWithDescription"> <span>Только с описанием</span> </label> </div> <div class="tree-search-panel"> <div class="search-modes"> <label> <input type="radio" name="search-mode" value="instant" checked> <span>Мгновенный поиск</span> </label> <label> <input type="radio" name="search-mode" value="button"> <span>Поиск по кнопке</span> </label> </div> <div class="tree-search-box no-button"> <input type="text" id="iconsSearch" placeholder="Поиск по названию или описанию..." class="tree-search-input" > <button id="clearSearch" class="tree-search-clear" title="Очистить поиск">×</button> <button id="searchBtn" class="tree-search-btn hidden">Найти</button> </div> </div> </div> <div class="icons-table-container"> <table class="icons-table" id="iconsTable"> <thead> <tr> <th>Иконка</th> <th>Название</th> <th>Описание</th> <th>Размер</th> <th>Код для вставки</th> </tr> </thead> <tbody> {{ range $name, $svg := $icons }} {{ $description := index $descriptions $name | default "" }} <tr class="icon-row" data-name="{{ $name | lower }}" data-description="{{ $description | lower }}" data-has-description="{{ if $description }}true{{ else }}false{{ end }}"> <td class="icon-cell"> <div class="icon-svg-small">{{ $svg | safeHTML }}</div> </td> <td class="name-cell"> <code>{{ $name }}</code> </td> <td class="description-cell"> {{ if $description }} {{ $description }} {{ else }} <span class="no-description">—</span> {{ end }} </td> <td class="size-cell"> 16×16 px </td> <td class="code-cell"> <div class="code-snippet" onclick="copyCode(this)"> <code>{{ "{{<" }}icon "{{ $name }}"{{ ">"}}}}</code> <span class="copy-tooltip">Кликните для копирования</span> <div class="copy-success"> <div class="success-icon">✓</div> <div>Скопировано!</div> </div> </div> </td> </tr> {{ end }} </tbody> </table> </div> <div class="no-results" id="noResults" style="display: none;"> <p>Иконки не найдены. Попробуйте другой запрос.</p> </div> {{ else }} <div class="error-message"> <h3>Ошибка: данные иконок не найдены!</h3> <p>Проверьте файл data/icons.yaml</p> </div> {{ end }} </div>
<style> .icons-gallery { margin: 40px 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
.icons-controls { display: grid; grid-template-columns: auto 1fr; gap: 20px; margin-bottom: 20px; align-items: start; }
.icons-stats { background: #f8f9fa; padding: 12px 20px; border-radius: 8px; font-size: 14px; color: #555; white-space: nowrap; }
.search-filters { grid-column: 1; grid-row: 2; }
.filter-checkbox { display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 14px; color: #555; padding: 8px 12px; border-radius: 6px; transition: background 0.3s ease; }
.filter-checkbox:hover { background: #f0f0f0; }
.filter-checkbox input[type="checkbox"] { margin: 0; }
.tree-search-panel { grid-column: 2; grid-row: 1 / span 2; display: flex; flex-direction: column; gap: 12px; }
.search-modes { display: flex; gap: 20px; font-size: 14px; color: #555; }
.search-modes label { display: flex; align-items: center; gap: 8px; cursor: pointer; }
.search-modes input[type="radio"] { margin: 0; }
.tree-search-box { position: relative; display: flex; margin-bottom: 8px; height: 36px; align-items: center; }
.tree-search-input { flex: 1; height: 100%; padding: 0 30px 0 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; box-sizing: border-box; transition: border-color 0.2s; }
.tree-search-input:focus { border-color: #2188ff; outline: none; }
.tree-search-btn { height: 36px; margin-left: 8px; padding: 0 12px; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; flex-shrink: 0; transition: opacity 0.2s, width 0.2s, margin 0.2s; }
.tree-search-btn:hover { background: #68aeff; }
.tree-search-btn.hidden { opacity: 0; width: 0; margin-left: 0; padding: 0; overflow: hidden; }
.tree-search-clear { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); background: none; border: none; cursor: pointer; font-size: 18px; color: #999; transition: all 0.2s; width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; }
.tree-search-clear:hover { color: #666; }
.tree-search-box:not(.no-button) .tree-search-clear { right: 90px; }
.icons-table-container { overflow-x: auto; border: 1px solid #e1e5e9; border-radius: 8px; background: white; margin-top: 10px; }
.icons-table { width: 100%; border-collapse: collapse; }
.icons-table th { background: #f8f9fa; padding: 16px; text-align: left; font-weight: 600; color: #555; border-bottom: 2px solid #e1e5e9; }
.icons-table td { padding: 12px 16px; border-bottom: 1px solid #f0f0f0; }
.icon-row.hidden { display: none; }
.icon-row:hover { background: #fafafa; }
.icon-cell { text-align: center; width: 60px; }
.icon-svg-small { width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; margin: 0 auto; }
.icon-svg-small svg { width: 16px; height: 16px; min-width: 16px; min-height: 16px; }
.name-cell { font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 13px; color: #333; width: 150px; }
.name-cell code { background: #f5f5f5; padding: 4px 8px; border-radius: 4px; border: 1px solid #eee; font-size: 12px; }
.description-cell { color: #666; font-size: 14px; line-height: 1.4; max-width: 300px; }
.no-description { color: #999; font-style: italic; }
.size-cell { color: #888; font-size: 12px; text-align: center; width: 80px; }
.code-cell { width: 200px; }
.code-snippet { position: relative; background: #f8f9fa; padding: 10px 14px; border-radius: 6px; border: 1px solid #e9ecef; cursor: pointer; transition: all 0.3s ease; font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 13px; min-height: 40px; display: flex; align-items: center; }
.code-snippet:hover { background: #e3f2fd; border-color: #2196f3; }
.code-snippet code { background: none; border: none; padding: 0; color: #1976d2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block; flex: 1; }
.copy-tooltip { position: absolute; top: -35px; left: 50%; transform: translateX(-50%); background: #333; color: white; padding: 6px 12px; border-radius: 4px; font-size: 12px; opacity: 0; transition: opacity 0.3s ease; pointer-events: none; white-space: nowrap; z-index: 1000; }
.code-snippet:hover .copy-tooltip { opacity: 1; }
.copy-success { position: absolute; top: -60px; left: 50%; transform: translateX(-50%); background: #4caf50; color: white; padding: 10px 15px; border-radius: 6px; font-size: 12px; opacity: 0; transition: opacity 0.3s ease; pointer-events: none; z-index: 1000; text-align: center; display: flex; flex-direction: column; align-items: center; gap: 4px; }
.copy-success.show { opacity: 1; }
.success-icon { font-size: 16px; font-weight: bold; }
.no-results { text-align: center; padding: 40px; color: #888; font-style: italic; display: none; }
.error-message { color: #d32f2f; padding: 20px; border: 2px solid #ffcdd2; border-radius: 8px; background: #ffebee; }
.hidden { display: none !important; }
/* Адаптивность */ @media (max-width: 1024px) { .icons-controls { grid-template-columns: 1fr; gap: 15px; } .tree-search-panel { grid-column: 1; grid-row: 3; } .description-cell { display: none; } .code-cell { width: 180px; } }
@media (max-width: 768px) { .search-modes { flex-direction: column; gap: 10px; } .icons-table th, .icons-table td { padding: 10px 12px; } .code-cell { display: none; } .name-cell { width: 120px; } } </style>
<script> document.addEventListener('DOMContentLoaded', function() { // Элементы управления const searchInput = document.getElementById('iconsSearch'); const searchBtn = document.getElementById('searchBtn'); const clearBtn = document.getElementById('clearSearch'); const modeRadios = document.querySelectorAll('input[name="search-mode"]'); const descriptionFilter = document.getElementById('showWithDescription');
let currentSearchTerm = ''; let currentMode = 'instant'; let searchTimeout; let showOnlyWithDescription = false;
// Инициализация режима поиска function initSearchMode() { currentMode = document.querySelector('input[name="search-mode"]:checked').value; const searchBox = document.querySelector('.tree-search-box');
// Удаляем все обработчики searchInput.removeEventListener('input', handleInstantSearch);
if (currentMode === 'instant') { // Мгновенный поиск searchInput.addEventListener('input', handleInstantSearch); searchBtn.classList.add('hidden'); searchBox.classList.add('no-button'); } else { // Поиск по кнопке searchBtn.classList.remove('hidden'); searchBox.classList.remove('no-button'); } }
// Основная функция поиска и фильтрации function performSearch() { const searchTerm = searchInput.value.toLowerCase(); const rows = document.querySelectorAll('.icon-row'); const noResults = document.getElementById('noResults'); let visibleCount = 0;
rows.forEach(row => { const iconName = row.getAttribute('data-name'); const iconDescription = row.getAttribute('data-description'); const hasDescription = row.getAttribute('data-has-description') === 'true';
// Проверяем фильтр по описанию const descriptionFilter = !showOnlyWithDescription || hasDescription;
// Проверяем поисковый запрос const searchFilter = searchTerm === '' || iconName.includes(searchTerm) || iconDescription.includes(searchTerm);
// Показываем/скрываем строку if (descriptionFilter && searchFilter) { row.classList.remove('hidden'); visibleCount++; } else { row.classList.add('hidden'); } });
// Показываем/скрываем сообщение "нет результатов" noResults.style.display = visibleCount === 0 && searchTerm !== '' ? 'block' : 'none'; }
// Функция очистки поиска function clearSearch() { searchInput.value = ''; searchInput.focus(); performSearch(); }
// Обработчики событий function handleInstantSearch() { clearTimeout(searchTimeout); searchTimeout = setTimeout(performSearch, 300); }
function handleButtonSearch() { performSearch(); }
function toggleDescriptionFilter() { showOnlyWithDescription = descriptionFilter.checked; performSearch(); }
// Функция для копирования кода function copyCode(element) { const code = element.querySelector('code').textContent; navigator.clipboard.writeText(code).then(() => { const success = element.querySelector('.copy-success'); success.classList.add('show'); setTimeout(() => { success.classList.remove('show'); }, 2000); }).catch(err => { // Fallback const textArea = document.createElement('textarea'); textArea.value = code; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); const success = element.querySelector('.copy-success'); success.classList.add('show'); setTimeout(() => { success.classList.remove('show'); }, 2000); }); }
// Инициализация initSearchMode(); performSearch();
// Обработчики событий searchBtn.addEventListener('click', handleButtonSearch); clearBtn.addEventListener('click', clearSearch); descriptionFilter.addEventListener('change', toggleDescriptionFilter); searchInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { e.preventDefault(); performSearch(); } });
modeRadios.forEach(radio => { radio.addEventListener('change', initSearchMode); });
// Изоляция событий для предотвращения конфликта с глобальным поиском const stopEvents = (e) => { e.stopPropagation(); };
searchInput.addEventListener('keydown', stopEvents, true); searchInput.addEventListener('keyup', stopEvents, true); searchInput.addEventListener('input', stopEvents, true); searchInput.addEventListener('focus', stopEvents, true); searchInput.addEventListener('click', stopEvents, true); }); </script>
|
<script> document.addEventListener('DOMContentLoaded', function() { // Элементы управления const searchInput = document.getElementById('iconsSearch'); const searchBtn = document.getElementById('searchBtn'); const clearBtn = document.getElementById('clearSearch'); const modeRadios = document.querySelectorAll('input[name="search-mode"]'); const descriptionFilter = document.getElementById('showWithDescription');
let currentSearchTerm = ''; let currentMode = 'instant'; let searchTimeout; let showOnlyWithDescription = false;
// Инициализация режима поиска function initSearchMode() { currentMode = document.querySelector('input[name="search-mode"]:checked').value; const searchBox = document.querySelector('.tree-search-box');
// Удаляем все обработчики searchInput.removeEventListener('input', handleInstantSearch); searchInput.removeEventListener('keypress', handleEnterPress);
if (currentMode === 'instant') { // Мгновенный поиск searchInput.addEventListener('input', handleInstantSearch); searchInput.addEventListener('keypress', handleEnterPress); searchBtn.classList.add('hidden'); searchBox.classList.add('no-button'); } else { // Поиск по кнопке searchInput.addEventListener('keypress', handleEnterPress); searchBtn.classList.remove('hidden'); searchBox.classList.remove('no-button'); } // Принудительно обновляем поиск при смене режима performSearch(); }
// Основная функция поиска и фильтрации function performSearch() { const searchTerm = searchInput.value.toLowerCase(); const rows = document.querySelectorAll('.icon-row'); const noResults = document.getElementById('noResults'); let visibleCount = 0;
rows.forEach(row => { const iconName = row.getAttribute('data-name'); const iconDescription = row.getAttribute('data-description'); const hasDescription = row.getAttribute('data-has-description') === 'true';
// Проверяем фильтр по описанию const descriptionFilter = !showOnlyWithDescription || hasDescription;
// Проверяем поисковый запрос const searchFilter = searchTerm === '' || iconName.includes(searchTerm) || iconDescription.includes(searchTerm);
// Показываем/скрываем строку if (descriptionFilter && searchFilter) { row.classList.remove('hidden'); visibleCount++; } else { row.classList.add('hidden'); } });
// Показываем/скрываем сообщение "нет результатов" noResults.style.display = visibleCount === 0 && searchTerm !== '' ? 'block' : 'none'; }
// Функция очистки поиска function clearSearch() { searchInput.value = ''; searchInput.focus(); performSearch(); }
// Обработчики событий function handleInstantSearch() { clearTimeout(searchTimeout); searchTimeout = setTimeout(performSearch, 100); // Уменьшил задержку для мгновенности }
function handleButtonSearch() { performSearch(); }
function handleEnterPress(e) { if (e.key === 'Enter') { e.preventDefault(); performSearch(); } }
function toggleDescriptionFilter() { showOnlyWithDescription = descriptionFilter.checked; performSearch(); }
// Функция для копирования кода function copyCode(element) { const code = element.querySelector('code').textContent; navigator.clipboard.writeText(code).then(() => { const success = element.querySelector('.copy-success'); success.classList.add('show'); setTimeout(() => { success.classList.remove('show'); }, 2000); }).catch(err => { // Fallback const textArea = document.createElement('textarea'); textArea.value = code; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); const success = element.querySelector('.copy-success'); success.classList.add('show'); setTimeout(() => { success.classList.remove('show'); }, 2000); }); }
// Инициализация initSearchMode();
// Обработчики событий searchBtn.addEventListener('click', handleButtonSearch); clearBtn.addEventListener('click', clearSearch); descriptionFilter.addEventListener('change', toggleDescriptionFilter);
modeRadios.forEach(radio => { radio.addEventListener('change', initSearchMode); });
// Изоляция событий для предотвращения конфликта с глобальным поиском const stopEvents = (e) => { e.stopPropagation(); e.stopImmediatePropagation(); };
// Более агрессивная изоляция событий searchInput.addEventListener('keydown', stopEvents, true); searchInput.addEventListener('keyup', stopEvents, true); searchInput.addEventListener('keypress', stopEvents, true); searchInput.addEventListener('input', stopEvents, true); searchInput.addEventListener('focus', stopEvents, true); searchInput.addEventListener('click', stopEvents, true); searchInput.addEventListener('mousedown', stopEvents, true);
// Дополнительная глобальная защита document.addEventListener('keydown', function(e) { if (e.target === searchInput || document.activeElement === searchInput) { e.stopImmediatePropagation(); } }, true);
document.addEventListener('keyup', function(e) { if (e.target === searchInput || document.activeElement === searchInput) { e.stopImmediatePropagation(); } }, true);
document.addEventListener('input', function(e) { if (e.target === searchInput) { e.stopImmediatePropagation(); } }, true); }); </script>
|
<script> document.addEventListener('DOMContentLoaded', function() { // Элементы управления const searchInput = document.getElementById('iconsSearch'); const searchBtn = document.getElementById('searchBtn'); const clearBtn = document.getElementById('clearSearch'); const modeRadios = document.querySelectorAll('input[name="search-mode"]'); const descriptionFilter = document.getElementById('showWithDescription');
let searchTimeout; let showOnlyWithDescription = false;
// Основная функция поиска и фильтрации function performSearch() { const searchTerm = searchInput.value.toLowerCase(); const rows = document.querySelectorAll('.icon-row'); const noResults = document.getElementById('noResults'); let visibleCount = 0;
rows.forEach(row => { const iconName = row.getAttribute('data-name'); const iconDescription = row.getAttribute('data-description'); const hasDescription = row.getAttribute('data-has-description') === 'true';
// Проверяем фильтр по описанию const descriptionFilter = !showOnlyWithDescription || hasDescription;
// Проверяем поисковый запрос const searchFilter = searchTerm === '' || iconName.includes(searchTerm) || iconDescription.includes(searchTerm);
// Показываем/скрываем строку if (descriptionFilter && searchFilter) { row.classList.remove('hidden'); visibleCount++; } else { row.classList.add('hidden'); } });
// Показываем/скрываем сообщение "нет результатов" noResults.style.display = visibleCount === 0 && searchTerm !== '' ? 'block' : 'none'; }
// Функция очистки поиска function clearSearch() { searchInput.value = ''; searchInput.focus(); performSearch(); }
// Обработчики событий function handleInstantSearch() { clearTimeout(searchTimeout); searchTimeout = setTimeout(performSearch, 100); }
function handleButtonSearch() { performSearch(); }
function handleEnterPress(e) { if (e.key === 'Enter') { e.preventDefault(); performSearch(); } }
function toggleDescriptionFilter() { showOnlyWithDescription = descriptionFilter.checked; performSearch(); }
// Инициализация режима поиска function initSearchMode() { const currentMode = document.querySelector('input[name="search-mode"]:checked').value; const searchBox = document.querySelector('.tree-search-box');
// Удаляем все обработчики searchInput.removeEventListener('input', handleInstantSearch); searchInput.removeEventListener('keypress', handleEnterPress); searchBtn.removeEventListener('click', handleButtonSearch);
if (currentMode === 'instant') { // Мгновенный поиск searchInput.addEventListener('input', handleInstantSearch); searchInput.addEventListener('keypress', handleEnterPress); searchBtn.classList.add('hidden'); searchBox.classList.add('no-button'); } else { // Поиск по кнопке searchBtn.addEventListener('click', handleButtonSearch); searchInput.addEventListener('keypress', handleEnterPress); searchBtn.classList.remove('hidden'); searchBox.classList.remove('no-button'); } }
// Функция для копирования кода function copyCode(element) { const code = element.querySelector('code').textContent; navigator.clipboard.writeText(code).then(() => { const success = element.querySelector('.copy-success'); success.classList.add('show'); setTimeout(() => { success.classList.remove('show'); }, 2000); }).catch(err => { // Fallback const textArea = document.createElement('textarea'); textArea.value = code; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); const success = element.querySelector('.copy-success'); success.classList.add('show'); setTimeout(() => { success.classList.remove('show'); }, 2000); }); }
// Инициализация initSearchMode(); performSearch(); // Initial search to show all icons
// Обработчики событий clearBtn.addEventListener('click', clearSearch); descriptionFilter.addEventListener('change', toggleDescriptionFilter);
modeRadios.forEach(radio => { radio.addEventListener('change', initSearchMode); });
// Минимальная изоляция событий - только для предотвращения всплытия function stopPropagation(e) { if (e.target === searchInput) { e.stopPropagation(); } }
searchInput.addEventListener('keydown', stopPropagation); searchInput.addEventListener('keyup', stopPropagation); searchInput.addEventListener('input', stopPropagation); searchInput.addEventListener('focus', stopPropagation); }); </script>
|