Files
KefinTweaks/scripts/search.js
T

821 lines
33 KiB
JavaScript

// KefinTweaks Enhanced Search
// Enhanced search functionality with better UI and Jellyseerr integration
// Requires: cardBuilder.js, utils.js modules to be loaded before this script
(function() {
'use strict';
const LOG = (...args) => console.log('[KefinTweaks Search]', ...args);
const WARN = (...args) => console.warn('[KefinTweaks Search]', ...args);
const ERR = (...args) => console.error('[KefinTweaks Search]', ...args);
LOG('Initializing...');
/* const originalOpen = XMLHttpRequest.prototype.open;
const originalSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function (method, url, ...args) {
this.__url = url;
return originalOpen.apply(this, [method, url, ...args]);
};
XMLHttpRequest.prototype.send = function (...args) {
const url = this.__url || '';
const isSearchPage = location.hash.startsWith('#/search');
const isBlockedSearch =
isSearchPage &&
url.includes('/Items?') &&
url.includes('searchTerm=');
if (!isBlockedSearch) {
return originalSend.apply(this, args);
}
LOG('Blocking default Jellyfin search request:', url);
Dashboard.hideLoadingMsg();
// Async success without touching internal state
queueMicrotask(() => {
this.onload?.();
this.onreadystatechange?.();
});
}; */
// Configuration
const CONFIG = window.KefinTweaksConfig?.search || {
enableJellyseerr: false // Toggle for Jellyseerr integration
};
// config
const CORE_TYPES = ['Movie', 'Series', 'Episode', 'Person'];
const MUSIC_TYPES = ['MusicAlbum', 'Audio', 'MusicArtist', 'MusicVideo'];
const BOOKS_TYPES = ['Book', 'AudioBook'];
const OTHER_TYPES = ['Playlist', 'Photo', 'PhotoAlbum', 'LiveTvChannel', 'LiveTvProgram', 'TvChannel', 'TvProgram', 'BoxSet'];
const SEARCH_CACHE_TTL = 60000;
const SEARCH_ITEM_FIELDS = 'PrimaryImageAspectRatio,DateCreated,ProductionYear,RecursiveItemCount,ChildCount,ProviderIds,MediaSourceCount';
// Live UI search type for stale-paint guards (`videos` maps to internal `core`)
let activeSearchType = 'videos';
function toInternalSearchType(uiOrInternalType) {
if (!uiOrInternalType || uiOrInternalType === 'videos') return 'core';
return uiOrInternalType;
}
function getLiveSearchTerm() {
return (document.getElementById('searchTextInput')?.value || '').trim();
}
/** True when this run's term+type still match what the user has selected. */
function isSearchStillWanted(term, searchType) {
const liveTerm = getLiveSearchTerm();
if (!term || !liveTerm || term !== liveTerm) return false;
return toInternalSearchType(searchType) === toInternalSearchType(activeSearchType);
}
function ensureKefinNoItemsMessage(searchPage) {
if (!searchPage) return null;
let el = searchPage.querySelector('.noItemsMessage.kefin-noItemsMessage');
if (!el) {
// Migrate legacy empty dummy if present
const dummy = searchPage.querySelector('.noItemsMessage.dummy-section');
if (dummy) {
el = dummy;
el.classList.remove('dummy-section');
el.classList.add('kefin-noItemsMessage', 'centerMessage');
} else {
el = document.createElement('div');
el.className = 'noItemsMessage kefin-noItemsMessage centerMessage';
searchPage.appendChild(el);
}
}
return el;
}
function hideNativeNoItemsMessages(searchPage) {
if (!searchPage) return;
searchPage.querySelectorAll('.noItemsMessage:not(.kefin-noItemsMessage)').forEach((el) => {
el.style.display = 'none';
});
}
function setKefinNoItemsVisible(searchPage, { show, searchTerm } = {}) {
if (!searchPage) return;
const el = ensureKefinNoItemsMessage(searchPage);
if (!el) return;
if (show) {
hideNativeNoItemsMessages(searchPage);
const term = String(searchTerm || '').trim();
el.textContent = term
? `Sorry! No results found for "${term}"`
: 'Sorry! No results found';
el.style.display = '';
} else {
el.style.display = 'none';
}
}
async function searchSectionsHaveItems(sections) {
const list = (sections || []).filter(Boolean);
if (!list.length) return false;
const counts = await Promise.all(list.map(async (section) => {
try {
const raw = typeof section.result?.ensureData === 'function'
? await section.result.ensureData()
: await section.result?.dataPromise;
if (Array.isArray(raw)) return raw.length;
if (Array.isArray(raw?.Items)) return raw.Items.length;
return 0;
} catch {
return 0;
}
}));
return counts.some((n) => n > 0);
}
// Function to clear search results except jellyseerr-section
function clearSearchResultsExceptJellyseerr() {
LOG('Clearing search results except jellyseerr-section');
// Clear the smart search results container
const resultsContainer = document.getElementById('smart-search-results');
if (resultsContainer) {
resultsContainer.innerHTML = '';
}
const searchPage = document.getElementById('searchPage');
if (searchPage) setKefinNoItemsVisible(searchPage, { show: false });
}
function getTypeGroups(searchType) {
if (searchType === 'music') return MUSIC_TYPES;
if (searchType === 'books') return BOOKS_TYPES;
if (searchType === 'all') return [...CORE_TYPES, ...MUSIC_TYPES, ...BOOKS_TYPES, ...OTHER_TYPES];
return CORE_TYPES;
}
function postProcessSearchItems(itemType, data) {
const items = Array.isArray(data)
? data.slice()
: (Array.isArray(data?.Items) ? data.Items.slice() : []);
if (!items.length) return items;
if (itemType === 'MusicArtist') {
return items.sort((a, b) => {
const aIsFolder = a.IsFolder ? 1 : 0;
const bIsFolder = b.IsFolder ? 1 : 0;
return bIsFolder - aIsFolder;
});
}
return items.sort((a, b) => {
const aHasImage = a.ImageTags?.Primary ? 1 : 0;
const bHasImage = b.ImageTags?.Primary ? 1 : 0;
return bHasImage - aHasImage;
});
}
function buildSearchQueryDef(itemType, searchTerm) {
const term = searchTerm.trim();
if (itemType === 'MusicArtist') {
return {
path: '/Artists',
queryOptions: {
Limit: 100,
SearchTerm: term,
Fields: 'PrimaryImageAspectRatio',
ImageTypeLimit: 1,
EnableTotalRecordCount: false
}
};
}
if (itemType === 'Person') {
return {
path: '/Persons',
queryOptions: {
Limit: 100,
SearchTerm: term,
Fields: 'PrimaryImageAspectRatio',
ImageTypeLimit: 1,
EnableTotalRecordCount: false
}
};
}
return {
queryOptions: {
SearchTerm: term,
IncludeItemTypes: itemType,
Recursive: true,
Limit: 100,
Fields: SEARCH_ITEM_FIELDS,
ImageTypeLimit: 1,
EnableTotalRecordCount: false
}
};
}
function getSearchCardFormat(itemType) {
if (itemType === 'Episode') return 'Thumb';
if (itemType === 'MusicArtist' || itemType === 'Audio' || itemType === 'MusicAlbum') return 'Square';
return null;
}
async function buildSearchSection(itemType, searchTerm, order) {
const apiHelper = window.apiHelper;
if (!apiHelper?.getQuery || !apiHelper?.buildQueryFromSection) {
throw new Error('apiHelper.getQuery / buildQueryFromSection unavailable');
}
const userId = ApiClient.getCurrentUserId();
const serverUrl = ApiClient.serverAddress();
const query = buildSearchQueryDef(itemType, searchTerm);
const url = apiHelper.buildQueryFromSection(query, userId, serverUrl);
if (typeof url !== 'string') {
throw new Error(`Invalid search query URL for ${itemType}`);
}
const queryResult = await apiHelper.getQuery(url, {
useCache: true,
ttl: SEARCH_CACHE_TTL
});
const termKey = searchTerm.toLowerCase().trim().replace(/[^a-z0-9]+/gi, '-').slice(0, 48);
const cardFormat = getSearchCardFormat(itemType);
const sectionConfig = {
id: `search-${itemType}-${termKey}`,
name: getTypeDisplayName(itemType),
enabled: true,
order,
queries: [query],
ttl: SEARCH_CACHE_TTL,
userConfigurable: false,
...(cardFormat ? { cardFormat } : {})
};
let mappedDataPromise = null;
const ensureData = () => {
if (!mappedDataPromise) {
const raw = typeof queryResult.ensureData === 'function'
? queryResult.ensureData()
: queryResult.dataPromise;
mappedDataPromise = Promise.resolve(raw)
.then((data) => postProcessSearchItems(itemType, data))
.catch((err) => {
WARN('search ensureData failed for', itemType, err);
return [];
});
}
return mappedDataPromise;
};
const result = {
data: postProcessSearchItems(itemType, queryResult.data),
isStale: queryResult.isStale === true,
isStalePromise: queryResult.isStalePromise,
ensureData
};
Object.defineProperty(result, 'dataPromise', {
configurable: true,
enumerable: true,
get() {
return ensureData();
}
});
return { config: sectionConfig, result };
}
// styles (dedupe by id)
function addCustomStyles() {
if (document.getElementById('smart-search-styles')) return;
LOG('adding styles');
const style = document.createElement('style');
style.id = 'smart-search-styles';
style.textContent = `
.smart-search-input { width:100%; padding:8px; border-radius:4px; box-sizing:border-box; }
.smart-search-buttons { display:flex; gap:8px; margin-top:8px; flex-wrap:wrap; justify-content: center; }
.smart-search-btn { padding:8px 12px; border:none; border-radius:4px; cursor:pointer; background:#444; color:#fff; }
.smart-search-results { margin-top:12px; }
.smart-search-stats { color:#999; font-size:12px; margin-top:6px; display: none; }
#persistent-toggle-btn { display:none; position:relative; padding:8px 12px; background:#00a4dc; color:#fff; border:none; border-radius:4px; cursor:pointer; }
#searchPage .searchfields-icon {
top: 50%;
transform: translateY(-50%);
position: absolute;
z-index: 1;
right: 0;
cursor: pointer;
font-size: 2em;
}
`;
document.head.appendChild(style);
}
// update search URL helper
function updateSearchUrl(searchTerm, searchType = 'videos') {
try {
// Jellyfin uses hash-based routing, so we need to update the hash
const urlSuffix = ApiClient._appVersion.split('.')[1] > 10 ? '' : '.html';
const baseHash = `#/search${urlSuffix}`;
const params = new URLSearchParams();
const trimmed = searchTerm.trim();
if (trimmed) {
params.set('query', trimmed);
}
params.set('type', searchType);
const newHash = `${baseHash}?${params.toString()}`;
window.history.replaceState({}, '', window.location.pathname + newHash);
LOG('Updated search URL hash:', newHash);
} catch (e) {
ERR('updateSearchUrl error', e);
}
}
function getTypeDisplayName(itemType) {
const typeMap = {
'Movie': 'Movies',
'Series': 'TV Shows',
'Episode': 'Episodes',
'Person': 'People',
'MusicAlbum': 'Albums',
'Audio': 'Songs',
'MusicArtist': 'Artists',
'MusicVideo': 'Music Videos',
'Playlist': 'Playlists',
'Book': 'Books',
'AudioBook': 'Audiobooks',
'Photo': 'Photos',
'PhotoAlbum': 'Photo Albums',
'TvChannel': 'TV Channels',
'TvProgram': 'TV Programs',
'LiveTvChannel': 'Live TV Channels',
'LiveTvProgram': 'Live TV',
'BoxSet': 'Collections'
};
return typeMap[itemType] || itemType;
}
// Create smart search results container lazily when needed
function ensureSmartResultsContainer() {
let smartResults = document.getElementById('smart-search-results');
if (!smartResults) {
LOG('Creating smart-search-results container');
smartResults = document.createElement('div');
smartResults.id = 'smart-search-results';
smartResults.className = 'smart-search-results emby-scroller searchResults, padded-top, padded-bottom-page';
const searchPage = document.getElementById('searchPage');
if (searchPage) {
searchPage.appendChild(smartResults);
} else {
// Fallback to body if #searchPage not found
document.body.appendChild(smartResults);
}
}
return smartResults;
}
// perform smart search via progressive sections (Request/Jellyseerr stays separate)
async function performSmartSearch(searchTerm, searchType = 'core') {
LOG('performSmartSearch()', { searchTerm, searchType });
const searchPage = document.getElementById('searchPage');
if (!searchPage) {
ERR('searchPage not found');
return;
}
const trimmed = (searchTerm || '').trim();
const internalType = toInternalSearchType(searchType);
const resultsContainer = ensureSmartResultsContainer();
// Hide search suggestions while searching
const searchSuggestions = searchPage.querySelector('.searchSuggestions');
if (searchSuggestions) {
searchSuggestions.style.display = trimmed ? 'none' : 'block';
}
hideNativeNoItemsMessages(searchPage);
setKefinNoItemsVisible(searchPage, { show: false });
// Update URL with search query and type
const urlType = internalType === 'core' ? 'videos' : internalType;
updateSearchUrl(trimmed, urlType);
// Handle Jellyseerr-only search
if (internalType === 'request' || searchType === 'request') {
return;
}
if (!trimmed) {
resultsContainer.innerHTML = '';
setKefinNoItemsVisible(searchPage, { show: false });
return;
}
// Drop previous results as soon as a new search is committed
resultsContainer.innerHTML = '';
if (!window.cardBuilder?.renderProgressiveSections) {
ERR('cardBuilder.renderProgressiveSections unavailable');
return;
}
if (!isSearchStillWanted(trimmed, internalType)) {
LOG('Skipping search; UI already changed', { trimmed, internalType });
return;
}
try {
const typeGroups = getTypeGroups(internalType);
const sectionPromises = typeGroups.map((itemType, index) =>
buildSearchSection(itemType, trimmed, index).catch((err) => {
WARN('Failed to build search section for', itemType, err);
return null;
})
);
// Resolve section setup (cache lookup / getQuery shell) before paint
const sections = await Promise.all(sectionPromises);
if (!isSearchStillWanted(trimmed, internalType)) {
LOG('Skipping stale search render after fetch', { trimmed, internalType });
return;
}
const resolvedSections = sections.filter(Boolean);
const validSections = resolvedSections.map((section) => Promise.resolve(section));
if (!isSearchStillWanted(trimmed, internalType)) {
LOG('Skipping stale search paint', { trimmed, internalType });
return;
}
resultsContainer.innerHTML = '';
await window.cardBuilder.renderProgressiveSections(resultsContainer, validSections, {
showStaleDataBeforeRefresh: true
});
if (!isSearchStillWanted(trimmed, internalType)) {
LOG('Search superseded after paint; leaving newer owner in charge', { trimmed, internalType });
// Empty clear owns the DOM — remove anything this run may have painted
if (!getLiveSearchTerm()) {
resultsContainer.innerHTML = '';
setKefinNoItemsVisible(searchPage, { show: false });
}
return;
}
const hasItems = await searchSectionsHaveItems(resolvedSections);
if (!isSearchStillWanted(trimmed, internalType)) return;
setKefinNoItemsVisible(searchPage, { show: !hasItems, searchTerm: trimmed });
} catch (e) {
ERR('performSmartSearch error', e);
}
}
// main init
async function initSmartSearch() {
if (typeof ApiClient === 'undefined') { setTimeout(initSmartSearch, 500); return; }
const originalInput = document.getElementById('searchTextInput');
if (!originalInput) { setTimeout(initSmartSearch, 500); return; }
addCustomStyles();
// Handle URL query parameter on page load
// Parse query from hash since Jellyfin uses hash-based routing
let queryFromUrl = null;
if (window.location.hash.includes('#/search') && window.location.hash.includes('query=')) {
const hashQuery = window.location.hash.split('query=')[1];
if (hashQuery) {
queryFromUrl = hashQuery.split('&')[0]; // Get query before any other params
queryFromUrl = decodeURIComponent(queryFromUrl).replace(/\+/g, ' ');
}
}
LOG('initSmartSearch - URL query:', queryFromUrl);
LOG('initSmartSearch - originalInput exists:', !!originalInput);
if (originalInput) {
LOG('initSmartSearch - originalInput value:', originalInput.value);
}
if (queryFromUrl) {
LOG('initSmartSearch - URL query detected, leaving input alone');
}
// Replace original search input with our smart search input
let wrapper = document.getElementById('smart-search-wrapper');
if (!wrapper) {
wrapper = document.createElement('div');
wrapper.id = 'smart-search-wrapper';
wrapper.className = 'smart-search-wrapper';
// Create the search icon element
const searchIcon = document.createElement('span');
searchIcon.className = 'searchfields-icon material-icons search';
searchIcon.setAttribute('aria-hidden', 'true');
// Replace the original input with our smart search input
const input = originalInput.cloneNode(true);
input.id = 'searchTextInput'; // Use the same ID as original
input.className = 'smart-search-input emby-input searchfields-txtSearch';
input.type = 'text';
input.placeholder = 'Search...';
input.value = ''; // Clear any existing value
input.removeAttribute('data-jellyseerr-listener');
const btnRow = document.createElement('div');
btnRow.className = 'smart-search-buttons';
const smartSearchButtonClass = 'smart-search-btn emby-button flat';
const btnAll = document.createElement('button');
btnAll.id = 'smart-search-all';
btnAll.className = smartSearchButtonClass;
btnAll.textContent = 'All';
const btnCore = document.createElement('button');
btnCore.id = 'smart-search-core';
btnCore.className = smartSearchButtonClass + ' button-submit active';
btnCore.textContent = 'Movies/TV';
const btnMusic = document.createElement('button');
btnMusic.id = 'smart-search-music';
btnMusic.className = smartSearchButtonClass;
btnMusic.textContent = 'Music';
const btnBooks = document.createElement('button');
btnBooks.id = 'smart-search-books';
btnBooks.className = smartSearchButtonClass;
btnBooks.textContent = 'Books';
const libraries = await window.dataHelper.getLibraries();
// React may have remounted search during the await — use a live node
const existingWrapper = document.getElementById('smart-search-wrapper');
if (existingWrapper) {
LOG('Smart search wrapper already present after getLibraries; skipping insert');
wrapper = existingWrapper;
} else {
const liveInput = document.getElementById('searchTextInput');
if (!liveInput?.parentElement) {
WARN('Search input detached after getLibraries; retrying init');
setTimeout(initSmartSearch, 500);
return;
}
const hasVideoLibraries = libraries.some(library => library.CollectionType === 'movies' || library.CollectionType === 'tvshows' || library.CollectionType === 'homevideos');
const hasMusicLibraries = libraries.some(library => library.CollectionType === 'music' || library.CollectionType === 'musicvideos');
const hasBookLibraries = libraries.some(library => library.CollectionType === 'books');
btnRow.appendChild(btnAll);
if (hasVideoLibraries) {
btnRow.appendChild(btnCore);
}
if (hasMusicLibraries) {
btnRow.appendChild(btnMusic);
}
if (hasBookLibraries) {
btnRow.appendChild(btnBooks);
}
if (CONFIG.enableJellyseerr) {
const btnRequest = document.createElement('button');
btnRequest.id = 'smart-search-request';
btnRequest.className = smartSearchButtonClass;
btnRequest.textContent = 'Request';
btnRow.appendChild(btnRequest);
}
const stats = document.createElement('div');
stats.id = 'smart-search-stats';
stats.className = 'smart-search-stats';
// Create input wrapper
const inputWrapper = document.createElement('div');
inputWrapper.className = 'input-wrapper';
inputWrapper.appendChild(searchIcon);
inputWrapper.appendChild(input);
wrapper.appendChild(inputWrapper);
wrapper.appendChild(btnRow);
wrapper.appendChild(stats);
// Insert the wrapper before the live search input
liveInput.parentElement.insertBefore(wrapper, liveInput);
liveInput.remove();
input.focus();
}
}
// Smart results container will be created lazily when needed
const smartInput = document.getElementById('searchTextInput');
const smartCoreBtn = document.getElementById('smart-search-core');
const smartMusicBtn = document.getElementById('smart-search-music');
const smartBooksBtn = document.getElementById('smart-search-books');
const smartAllBtn = document.getElementById('smart-search-all');
const smartRequestBtn = document.getElementById('smart-search-request');
// Track current search type
let currentSearchType = 'videos';
// Function to update button states
function updateButtonStates(activeType) {
// Remove active class from all buttons
smartAllBtn.classList.remove('active','button-submit');
smartCoreBtn?.classList.remove('active','button-submit');
smartMusicBtn?.classList.remove('active','button-submit');
smartBooksBtn?.classList.remove('active','button-submit');
if (smartRequestBtn) {
smartRequestBtn.classList.remove('active','button-submit');
}
// Add active class to the selected button
if (activeType === 'all') {
smartAllBtn.classList.add('active','button-submit');
} else if (activeType === 'videos') {
smartCoreBtn?.classList.add('active','button-submit');
} else if (activeType === 'music') {
smartMusicBtn?.classList.add('active','button-submit');
} else if (activeType === 'books') {
smartBooksBtn?.classList.add('active','button-submit');
} else if (activeType === 'request' && smartRequestBtn) {
smartRequestBtn?.classList.add('active','button-submit');
}
}
// Function to handle search type toggle
function setSearchType(type) {
LOG('setSearchType called:', { type, currentSearchType });
currentSearchType = type;
activeSearchType = type;
updateButtonStates(type);
const searchTerm = smartInput.value.trim();
LOG('Search term from input:', searchTerm);
if (!searchTerm) return;
const internalType = toInternalSearchType(type);
LOG('Internal type mapping:', { type, internalType });
if (type === 'request') {
LOG('Request type - clearing results and performing fresh Jellyseerr search');
clearSearchResultsExceptJellyseerr();
performSmartSearch(searchTerm, 'request');
return;
}
performSmartSearch(searchTerm, internalType);
}
if (!wrapper.dataset.initialized) {
wrapper.dataset.initialized = 'true';
let timer = null;
const doSmartSearchDebounced = () => {
if (timer) clearTimeout(timer);
const term = (smartInput.value || '').trim();
if (!term) {
timer = null;
const resultsContainer = ensureSmartResultsContainer();
resultsContainer.innerHTML = '';
updateSearchUrl('', currentSearchType);
// Show search suggestions
const searchSuggestions = document.querySelector('.searchSuggestions');
if (searchSuggestions) {
searchSuggestions.style.display = 'block';
}
const searchPageEl = document.getElementById('searchPage');
if (searchPageEl) setKefinNoItemsVisible(searchPageEl, { show: false });
return;
}
const internalType = toInternalSearchType(currentSearchType);
timer = setTimeout(() => performSmartSearch(term, internalType), 300);
};
smartInput.addEventListener('input', doSmartSearchDebounced);
smartInput.addEventListener('keydown', (e)=>{
if(e.key==='Enter'){
e.preventDefault();
if (timer) clearTimeout(timer);
const internalType = toInternalSearchType(currentSearchType);
performSmartSearch((smartInput.value||'').trim(), internalType);
}
});
smartCoreBtn?.addEventListener('click', ()=>setSearchType('videos'));
smartMusicBtn?.addEventListener('click', ()=>setSearchType('music'));
smartBooksBtn?.addEventListener('click', ()=>setSearchType('books'));
smartAllBtn.addEventListener('click', ()=>setSearchType('all'));
if (smartRequestBtn) {
smartRequestBtn.addEventListener('click', ()=>setSearchType('request'));
}
// Parse type parameter from URL
let typeFromUrl = 'videos'; // default
if (window.location.hash.includes('type=')) {
const typeMatch = window.location.hash.match(/type=([^&]+)/);
if (typeMatch) {
typeFromUrl = typeMatch[1];
}
}
// Set initial search type and update UI
currentSearchType = typeFromUrl;
activeSearchType = typeFromUrl;
updateButtonStates(typeFromUrl);
// Handle URL query parameter - populate smart search and trigger search
if (queryFromUrl) {
smartInput.value = queryFromUrl;
// Trigger immediate search with the URL query
const internalType = toInternalSearchType(typeFromUrl);
performSmartSearch(queryFromUrl, internalType);
}
}
// persistent toggle button
let toggleBtn = document.getElementById('persistent-toggle-btn');
if (!toggleBtn) {
toggleBtn = document.createElement('button');
toggleBtn.id = 'persistent-toggle-btn';
toggleBtn.textContent = 'Switch to Default Search';
const searchFields = document.querySelector('.searchFields');
if (searchFields) {
searchFields.appendChild(toggleBtn);
} else {
// Fallback to body if .searchFields not found
document.body.appendChild(toggleBtn);
}
}
let smartMode = true;
const setMode = (isSmart) => {
smartMode = isSmart;
LOG('setMode', smartMode);
if (smartMode) {
wrapper.style.display = '';
// Hide search results containers (be more specific to avoid affecting other pages)
const searchResults = document.querySelectorAll('#searchPage .searchResults, #searchPage .padded-top, #searchPage .padded-bottom-page');
searchResults.forEach(sr => sr.style.display = 'none');
const resultsContainer = ensureSmartResultsContainer();
resultsContainer.style.display = '';
toggleBtn.textContent='Switch to Default Search';
} else {
wrapper.style.display = 'none';
// Show search results containers (be more specific to avoid affecting other pages)
const searchResults = document.querySelectorAll('#searchPage .searchResults, #searchPage .padded-top, #searchPage .padded-bottom-page');
searchResults.forEach(sr => sr.style.display = '');
const resultsContainer = ensureSmartResultsContainer();
resultsContainer.style.display = 'none';
toggleBtn.textContent='Switch to Smart Search';
}
};
toggleBtn.addEventListener('click', ()=>setMode(!smartMode));
// initial mode
setMode(true);
}
// Initialize search hook using utils
function initializeSearchHook() {
if (!window.KefinTweaksUtils) {
WARN('KefinTweaksUtils not available, retrying in 1 second');
setTimeout(initializeSearchHook, 1000);
return;
}
LOG('Registering search page handler with KefinTweaksUtils');
// Register handler for search page
window.KefinTweaksUtils.onViewPage((view, element) => {
LOG('Search page detected via utils');
// Small delay to ensure DOM is ready
setTimeout(() => {
const searchInput = document.getElementById('searchTextInput');
if (searchInput && !document.getElementById('smart-search-wrapper')) {
LOG('Search input found, initializing smart search');
initSmartSearch();
}
}, 100);
}, {
pages: ['search']
});
LOG('Search hook initialized successfully');
}
// Initialize the hook when the script loads
//initializeSearchHook();
if (document.readyState==='loading') {
document.addEventListener('DOMContentLoaded', () => {
initializeSearchHook();
});
} else {
initializeSearchHook();
}
LOG('Initialized successfully');
})();