1import { decorateIcons } from '../../scripts/aem.js'; 2// eslint-disable-next-line import/no-cycle 3import { RECENT_KEY } from '../../scripts/constants.js'; 4import { 5 div, 6 span, 7 li, 8} from '../../scripts/dom-builder.js'; 9import { trackClickEvent } from '../../scripts/tracking/page_click_event.js'; 10import { EventName } from '../../scripts/tracking/page_constants.js'; 11import { locale } from './header-utils.js'; 12 13// Key for storing recent searches in localStorage 14export default function renderRecentSearch() { 15 // Always use the unified search-suggestions class for both desktop and mobile 16 const searchLists = document.querySelectorAll('ul.search-suggestions'); 17 if (!searchLists.length) return; 18 19 // Get last 5 unique recent search terms (case-insensitive, preserve order) 20 let items = []; 21 try { 22 const raw = JSON.parse(localStorage.getItem(RECENT_KEY) || '[]'); 23 const seen = new Set(); 24 raw.forEach((term) => { 25 if (items.length >= 5) return; 26 const key = term.trim().toLowerCase(); 27 if (!seen.has(key)) { 28 seen.add(key); 29 items.push(term); 30 } 31 }); 32 } catch { 33 items = []; 34 } 35 36 // If no items, clear and hide all lists 37 if (!items.length) { 38 searchLists.forEach((searchList) => { 39 searchList.innerHTML = ''; 40 searchList.parentElement?.classList.add('hidden'); 41 }); 42 return; 43 } 44 45 // Render header and items for each list 46 searchLists.forEach((searchList) => { 47 searchList.innerHTML = ''; 48 searchList.parentElement?.classList.remove('hidden'); 49 searchList.parentElement?.classList.remove('border-b', 'border-grey-dim'); 50 searchList.nextElementSibling?.classList.add('hidden'); 51 searchList.classList.remove('py-4', 'pb-4', 'border-b', 'border-grey-dim'); 52 searchList.classList.add('pb-4'); 53 54 // Header row 55 const headerLi = li( 56 { class: 'flex justify-between items-center mb-2 px-4 pt-4' }, 57 span( 58 { 59 class: 60 'font-proxima font-bold text-[1.125rem] leading-[1] tracking-normal text-grey-45', 61 }, 62 'Recent Searches', 63 ), 64 span( 65 { 66 class: 67 'font-proxima font-normal text-[0.875rem] leading-[1] tracking-normal text-grey-45 cursor-pointer hover:underline', 68 }, 69 'Clear', 70 ), 71 ); 72 searchList.appendChild(headerLi); 73 74 // Clear all lists when 'Clear' is clicked 75 headerLi.lastChild.addEventListener('click', () => { 76 localStorage.removeItem(RECENT_KEY); 77 renderRecentSearch(); 78 }); 79 80 // Render each unique recent search term 81 items.forEach((term) => { 82 const item = li( 83 { 84 class: 85 'group flex justify-between items-center py-2 hover:bg-grey-55 px-4 !cursor-pointer', 86 }, 87 span( 88 { 89 onclick: (e) => { 90 e.preventDefault(); 91 window.location.assign( 92 `${(window.location.origin.includes('phx') || window.location.origin.includes('localhost') ? `${locale}/` : '/')}search-result#q=${encodeURIComponent(term)}`, 93 ); 94 setTimeout(() => window.location.reload(), 1000); 95 trackClickEvent({ eventName: EventName.SEARCH, clickTitle: term }); 96 // Remove focus from input boxes immediately 97 document.querySelectorAll('.search-bar-desktop, .search-bar-mobile').forEach((inputEl) => { 98 inputEl.blur(); 99 }); 100 101 // Close suggestions container 102 document.querySelectorAll('.suggestions-container').forEach((container) => { 103 container.classList.add('hidden'); 104 }); 105 106 // Use same logic as enteredRedirect to avoid page refresh on search-result page 107 const isOnSearchPage = window.location.pathname.includes('/search-result'); 108 if (isOnSearchPage) { 109 // Just update hash - Coveo's URL manager will handle the rest 110 window.location.hash = `q=${encodeURIComponent(term)}`; 111 } else { 112 // Navigate to search page from other pages 113 window.location.assign( 114 `${(window.location.origin.includes('phx') || window.location.origin.includes('localhost') ? `${locale}/` : '/')}search-result#q=${encodeURIComponent(term)}`, 115 ); 116 } 117 }, 118 class: 'flex items-center gap-2 hover:no-underline w-full break-all', 119 }, 120 div( 121 { class: 'h-5 w-5 flex items-center justify-center' }, 122 span({ 123 class: 'icon icon-recent-history w-[0.980625rem] h-[0.980625rem]', 124 }), 125 ), 126 span( 127 { 128 class: 129 'py-0.5 font-proxima font-normal not-italic text-base leading-none tracking-normal text-grey-45 no-underline group-hover:underline group-hover:decoration-solid group-hover:decoration-skip-ink-auto cursor-pointer', 130 }, 131 term, 132 ), 133 ), 134 div( 135 { class: 'h-6 w-6 flex items-center justify-center cursor-pointer' }, 136 span({ 137 class: 138 'icon icon-cross w-[0.564375rem] h-[0.564375rem] cursor-pointer', 139 }), 140 ), 141 ); 142 143 // Remove item from all lists when cross is clicked 144 item.querySelector('.icon-cross').addEventListener('click', (e) => { 145 e.preventDefault(); 146 e.stopPropagation(); 147 let raw = []; 148 try { 149 raw = JSON.parse(localStorage.getItem(RECENT_KEY) || '[]'); 150 } catch { 151 // Ignore parse errors, use empty array 152 }
153 const key = term.trim().toLowerCase(); 154 const updated = raw.filter((t) => t.trim().toLowerCase() !== key); 155 localStorage.setItem(RECENT_KEY, JSON.stringify(updated)); 156 renderRecentSearch(); 157 }); 158 159 searchList.appendChild(item); 160 }); 161 162 decorateIcons(searchList); 163 }); 164}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.