diff --git a/languages/en.yaml b/languages/en.yaml index fb9ba10e..d7d2c72e 100644 --- a/languages/en.yaml +++ b/languages/en.yaml @@ -56,6 +56,7 @@ PLUGIN_ADMIN: MODAL_UPDATE_GRAV_CONFIRMATION_REQUIRED_DESC: "You are about to upgrade Grav to the latest version available. Would you like to continue?" ADD_FILTERS: "Add Filters" SEARCH_PAGES: "Search Pages" + SEARCH_CONFIGURATION: "Search settings..." VERSION: "Version" WAS_MADE_WITH: "Was made with" BY: "By" diff --git a/themes/grav/css-compiled/config-search.css b/themes/grav/css-compiled/config-search.css new file mode 100644 index 00000000..cc2b3524 --- /dev/null +++ b/themes/grav/css-compiled/config-search.css @@ -0,0 +1,96 @@ +/* Config Search Feature */ +.config-search-wrapper { + padding: 0.6rem 1.5rem; + border-bottom: 1px solid #e0e0e0; + background: #fafafa; +} + +.config-search-inner { + position: relative; + display: inline-flex; + align-items: center; + max-width: 360px; + width: 100%; +} + +.config-search-icon { + position: absolute; + left: 10px; + color: #999; + pointer-events: none; + font-size: 0.8rem; +} + +#config-search-input { + width: 100%; + padding: 6px 28px 6px 30px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 0.85rem; + background: #fff; + color: #333; + outline: none; + transition: border-color 0.15s ease, box-shadow 0.15s ease; + line-height: 1.4; +} + +#config-search-input:focus { + border-color: #4990e2; + box-shadow: 0 0 0 2px rgba(73, 144, 226, 0.15); +} + +#config-search-input::placeholder { + color: #bbb; +} + +.config-search-clear { + position: absolute; + right: 7px; + background: none; + border: none; + cursor: pointer; + color: #bbb; + display: none; + padding: 2px 3px; + font-size: 0.75rem; + line-height: 1; + align-items: center; + justify-content: center; +} + +.config-search-clear:hover { + color: #666; +} + +/* Badge showing match count in tab nav link */ +.search-count-badge { + display: inline-block; + background: #4990e2; + color: #fff; + font-size: 0.6rem; + font-weight: bold; + padding: 1px 5px; + border-radius: 10px; + margin-left: 6px; + vertical-align: middle; + line-height: 1.6; + min-width: 16px; + text-align: center; +} + +/* Hide tabs with no matches */ +.tabs-nav .tab__link.search-no-match { + display: none !important; +} + +/* Highlight matching form field rows */ +.form-field.grid.search-highlight { + background-color: rgba(255, 218, 30, 0.18); + border-radius: 3px; + box-shadow: inset 3px 0 0 rgba(240, 190, 0, 0.5); +} + +/* Hide sections with no matches on flat (non-tabbed) layouts */ +.block.search-hidden { + display: none; +} diff --git a/themes/grav/js/config-search.js b/themes/grav/js/config-search.js new file mode 100644 index 00000000..6ab93e48 --- /dev/null +++ b/themes/grav/js/config-search.js @@ -0,0 +1,113 @@ +(function () { + var input = document.getElementById('config-search-input'); + var clearBtn = document.getElementById('config-search-clear'); + if (!input || !clearBtn) return; + + var form = document.getElementById('blueprints'); + if (!form) return; + + var debounceTimer; + + function normalize(text) { + return text.toLowerCase().replace(/\s+/g, ' ').trim(); + } + + function clearState() { + form.querySelectorAll('.form-field.grid.search-highlight').forEach(function (el) { + el.classList.remove('search-highlight'); + }); + form.querySelectorAll('.search-count-badge').forEach(function (el) { + el.remove(); + }); + form.querySelectorAll('.tab__link.search-no-match').forEach(function (el) { + el.classList.remove('search-no-match'); + }); + form.querySelectorAll('.block.search-hidden').forEach(function (el) { + el.classList.remove('search-hidden'); + }); + } + + function matchFields(container, query) { + var count = 0; + container.querySelectorAll('.form-field.grid').forEach(function (field) { + var label = field.querySelector('.form-label'); + if (label && normalize(label.textContent).indexOf(query) !== -1) { + field.classList.add('search-highlight'); + count++; + } + }); + return count; + } + + function searchTabbed(container, query) { + var tabLinks = container.querySelectorAll('.tabs-nav .tab__link'); + var firstMatch = null; + + tabLinks.forEach(function (link) { + var tabId = link.getAttribute('data-tabid'); + var content = tabId ? document.getElementById(tabId) : null; + if (!content) return; + + var count = matchFields(content, query); + + if (count > 0) { + link.classList.remove('search-no-match'); + var span = link.querySelector('span'); + if (span) { + var badge = document.createElement('span'); + badge.className = 'search-count-badge'; + badge.textContent = count; + span.appendChild(badge); + } + if (!firstMatch) firstMatch = link; + } else { + link.classList.add('search-no-match'); + } + }); + + // Auto-switch if active tab has no matches + if (firstMatch) { + var active = container.querySelector('.tabs-nav .tab__link.active'); + if (!active || active.classList.contains('search-no-match')) { + firstMatch.dispatchEvent(new MouseEvent('click', { bubbles: true })); + } + } + } + + function searchFlat(query) { + form.querySelectorAll('.block.block-section').forEach(function (block) { + var count = matchFields(block, query); + if (count === 0) { + block.classList.add('search-hidden'); + } + }); + } + + function runSearch(rawQuery) { + clearState(); + + var query = normalize(rawQuery); + if (!query) return; + + var sideTabs = form.querySelector('.form-tabs.side-tabs'); + if (sideTabs) { + searchTabbed(sideTabs, query); + } else { + searchFlat(query); + } + } + + input.addEventListener('input', function () { + var val = this.value; + clearBtn.style.display = val ? 'inline-flex' : 'none'; + clearTimeout(debounceTimer); + debounceTimer = setTimeout(function () { runSearch(val); }, 200); + }); + + clearBtn.addEventListener('click', function () { + input.value = ''; + clearBtn.style.display = 'none'; + runSearch(''); + input.focus(); + }); +}()); diff --git a/themes/grav/templates/config.html.twig b/themes/grav/templates/config.html.twig index 97db65d9..fd543353 100644 --- a/themes/grav/templates/config.html.twig +++ b/themes/grav/templates/config.html.twig @@ -7,6 +7,7 @@ {% do admin.redirect('config/' ~ config_slug, 302) %} {% endif %} {% set isInfo = (config_slug == 'info') %} +{% set isSearchable = config_slug in ['system', 'site'] %} {% set tab_title_string = "PLUGIN_ADMIN." ~ config_slug|upper %} {% set tab_title = (tab_title_string|t == tab_title_string ? config_slug|capitalize : tab_title_string|t) ?: 'Not Found' %} @@ -18,10 +19,16 @@ {% block stylesheets %} {% do assets.addCss(theme_url ~ '/css/codemirror/codemirror.css') %} + {% if isSearchable %} + {% do assets.addCss(theme_url ~ '/css-compiled/config-search.css') %} + {% endif %} {{ parent() }} {% endblock %} {% block javascripts %} + {% if isSearchable %} + {% do assets.addJs(theme_url ~ '/js/config-search.js', {'group': 'bottom', 'loading': 'defer'}) %} + {% endif %} {{ parent() }} {% endblock %} @@ -50,6 +57,15 @@ {% endfor %} + {% if isSearchable %} +
+
+ + + +
+
+ {% endif %} {% endblock %} {% block content %}