export const normalizeLocal = value => String(value).normalize('NFD').replace(/[\u0300-\u036f]/g,'').toLowerCase().replace(/[^a-z0-9]+/g,' ').trim(); export function selectLocalEntries(entries,{query='',town='',archives=false}={}) { const terms=normalizeLocal(query).split(' ').filter(Boolean); return entries.filter(entry=>(archives||!entry.archive)&&(!town||entry.town===town)&&terms.every(term=>normalizeLocal(entry.search).includes(term))); } export function localPage(entries,page=1,size=8){ const pages=Math.max(1,Math.ceil(entries.length/size)); const current=Math.max(1,Math.min(pages,page)); return {entries:entries.slice((current-1)*size,current*size),page:current,pages}; } if(typeof document!=='undefined') { const root=document.querySelector('[data-local-directory]'); if(root) initDirectory(root); } function initDirectory(root){ const query=root.querySelector('#local-query'),town=root.querySelector('#local-town'),archives=root.querySelector('#local-archives'); const form=root.querySelector('.local-filters'),count=root.querySelector('#local-result-count'),empty=root.querySelector('#local-empty'); const pagination=root.querySelector('#local-pagination'),previous=root.querySelector('#local-prev'),next=root.querySelector('#local-next'),pageStatus=root.querySelector('#local-page-status'); const heading=root.querySelector('#local-results-heading'); const entries=[...root.querySelectorAll('[data-local-card]')].map(el=>({el,town:el.dataset.town,search:el.dataset.search,archive:el.dataset.archive==='true'})); const panels=[...root.querySelectorAll('[data-local-panel]')],tabs=[...root.querySelectorAll('[data-local-tab]')]; let currentPage=1; const reduced=()=>matchMedia('(prefers-reduced-motion: reduce)').matches; function render({focus=false}={}){ const found=selectLocalEntries(entries,{query:query.value,town:town.value,archives:archives?.checked||false}); const page=localPage(found,currentPage);currentPage=page.page; const shown=new Set(page.entries); for(const entry of entries)entry.el.hidden=!shown.has(entry); const start=found.length?(page.page-1)*8+1:0,end=Math.min(page.page*8,found.length); count.textContent=found.length+' '+(found.length===1?'fiche':'fiches')+(found.length?' · '+start+'–'+end:''); empty.hidden=found.length>0;pagination.hidden=page.pages<=1; const archiveMatches=selectLocalEntries(entries,{query:query.value,town:town.value,archives:true}).length; const hint=root.querySelector('#local-empty-hint'); if(hint)hint.textContent=archiveMatches&&!found.length?'Cette recherche correspond à '+archiveMatches+' fiche(s) fermée(s) ou historique(s). Cochez « Inclure les fiches fermées ou historiques » pour les consulter.':'Essayez une autre commune, un code postal ou un nom plus court. L’absence de fiche ne signifie pas l’absence de professionnel.'; previous.disabled=page.page===1;next.disabled=page.page===page.pages; pageStatus.textContent='Page '+page.page+' sur '+page.pages; const national=root.querySelector('#local-national-search'); national.href='/item/?q='+encodeURIComponent(query.value.trim()||town.value); if(focus){heading.focus({preventScroll:true});heading.scrollIntoView({behavior:reduced()?'instant':'smooth',block:'start'});} } function changed(){currentPage=1;render();} query.addEventListener('input',changed);town.addEventListener('change',changed);archives?.addEventListener('change',changed); form.addEventListener('submit',e=>{e.preventDefault();changed();heading.focus({preventScroll:true});}); for(const reset of root.querySelectorAll('[data-local-reset]'))reset.addEventListener('click',()=>{form.reset();changed();query.focus();}); previous.addEventListener('click',()=>{currentPage--;render({focus:true});}); next.addEventListener('click',()=>{currentPage++;render({focus:true});}); // Keep every source and existing fragment address reachable in the secondary panel. const notes=root.querySelector('#local-notes'),topicSelect=root.querySelector('#local-topic'); for(const detail of notes.querySelectorAll('details')){detail.open=true;detail.querySelector('summary')?.addEventListener('click',e=>e.preventDefault());} const sections=[...notes.children]; const topics=[]; for(const section of sections){ const parts=[...section.querySelectorAll('.local-topic, details, #lot13-fiches-departement')]; if(parts.length){for(const el of parts)topics.push({el,section,label:el.querySelector('h3,summary,h2')?.textContent.trim()||'Repères locaux'});} else topics.push({el:section,section,label:section.querySelector('h2,h3')?.textContent.trim()||'Conseils pour votre recherche'}); } topics.forEach((topic,i)=>{const option=document.createElement('option');option.value=String(i);option.textContent=topic.label;topicSelect.append(option);}); function showTopic(index){ if(!topics.length)return; const target=topics[index]||topics[0]; for(const section of sections)section.hidden=section!==target.section; for(const topic of topics)if(topic.el!==topic.section)topic.el.hidden=topic!==target; topicSelect.value=String(topics.indexOf(target)); } topicSelect.addEventListener('change',()=>showTopic(Number(topicSelect.value))); function activate(panelId){ for(const panel of panels)panel.hidden=panel.id!==panelId; for(const tab of tabs){if(tab.dataset.localTab===panelId)tab.setAttribute('aria-current','true');else tab.removeAttribute('aria-current');} } function followHash({scroll=false}={}){ let id;try{id=decodeURIComponent(location.hash.slice(1));}catch{id='';} const target=id?document.getElementById(id):null; const panel=target?.closest('[data-local-panel]'); activate(panel?.id||'etablissements'); if(panel?.id==='reperes-locaux'){ const index=topics.findIndex(topic=>topic.el===target||topic.el.contains(target)); if(index>=0)showTopic(index); } const entry=entries.find(item=>item.el===target); if(entry){ form.reset();if(archives)archives.checked=true; const all=selectLocalEntries(entries,{archives:true}); currentPage=Math.floor(all.indexOf(entry)/8)+1;render(); } if(scroll&&target)target.scrollIntoView({behavior:reduced()?'instant':'smooth',block:'start'}); } window.addEventListener('hashchange',()=>followHash({scroll:true})); // Hashes remain real links and work without JavaScript; same-tab clicks also restore the panel. for(const tab of tabs)tab.addEventListener('click',()=>activate(tab.dataset.localTab)); const params=new URLSearchParams(location.search);if(params.has('q'))query.value=params.get('q').slice(0,80); root.querySelectorAll('.local-enhance').forEach(el=>el.hidden=false); form.dataset.enhanced='true';showTopic(topics.findIndex(topic=>topic.el.id==='local-advice'));render();followHash(); }