diff --git a/atlas/static/atlas/js/markup_inserter.js b/atlas/static/atlas/js/markup_inserter.js index ce6529c4..1bc7a4cc 100644 --- a/atlas/static/atlas/js/markup_inserter.js +++ b/atlas/static/atlas/js/markup_inserter.js @@ -40,7 +40,7 @@ btn.addEventListener('click', function(){ const t = btn.dataset.type; if(t === 'case'){ - const id = prompt('Case ID to link to:'); if(!id) return; insertAtCursor(textarea, '[['+t+':'+id+']]' ); + openCasePicker(textarea); } else if(t === 'finding'){ const id = prompt('Finding ID to link to:'); if(!id) return; insertAtCursor(textarea, '[['+t+':'+id+']]' ); } else if(t === 'displayset'){ @@ -122,4 +122,69 @@ attachToSelector: attachToSelector }; + // --- Case picker modal --- + function openCasePicker(textarea){ + try{ + console.log('Opening case picker for', textarea); + // Create overlay + const overlay = document.createElement('div'); + overlay.className = 'markup-case-picker-overlay'; + Object.assign(overlay.style, {position:'fixed',left:0,top:0,right:0,bottom:0,background:'rgba(0,0,0,0.4)',zIndex:1050,display:'flex',alignItems:'center',justifyContent:'center'}); + + const dialog = document.createElement('div'); + dialog.className = 'markup-case-picker-dialog card'; + Object.assign(dialog.style, {width:'min(900px,95%)',maxHeight:'80vh',overflow:'auto'}); + + const header = document.createElement('div'); header.className='card-header d-flex justify-content-between align-items-center'; + header.innerHTML = 'Select case'; + const closeBtn = document.createElement('button'); closeBtn.type='button'; closeBtn.className='btn-close'; closeBtn.setAttribute('aria-label','Close'); + closeBtn.addEventListener('click', function(){ document.body.removeChild(overlay); }); + header.appendChild(closeBtn); + + const body = document.createElement('div'); body.className='card-body'; + const input = document.createElement('input'); input.type='search'; input.className='form-control mb-2'; input.placeholder='Type to search cases...'; + const results = document.createElement('div'); results.className='case-search-results'; + body.appendChild(input); body.appendChild(results); + + dialog.appendChild(header); dialog.appendChild(body); overlay.appendChild(dialog); + document.body.appendChild(overlay); + input.focus(); + + var timer = null; + input.addEventListener('input', function(){ + if(timer) clearTimeout(timer); + timer = setTimeout(function(){ + var q = encodeURIComponent(input.value || ''); + // Fetch server-side partial that renders result list-group + var url = window.location.origin + '/atlas/search/cases/?q=' + q; + fetch(url, {credentials:'same-origin'}).then(function(r){ return r.text(); }).then(function(html){ + results.innerHTML = html; + }).catch(function(e){ console.error('case picker fetch error', e); results.innerHTML = '
Search failed
'; }); + }, 300); + }); + + // Delegate click on result items + results.addEventListener('click', function(e){ + var item = e.target.closest('.list-group-item'); + if(!item) return; + var pk = item.getAttribute('data-case-pk'); + if(!pk){ + var a = item.querySelector('a'); + if(a){ var m = a.getAttribute('href').match(/case\/(\d+)\/?.*$/); if(m) pk = m[1]; } + } + if(!pk) return; + insertAtCursor(textarea, '[['+'case:'+pk+']]' ); + // remove modal + if(overlay && overlay.parentNode) overlay.parentNode.removeChild(overlay); + }); + + // Allow ESC to close + overlay.addEventListener('keydown', function(ev){ if(ev.key === 'Escape'){ if(overlay.parentNode) overlay.parentNode.removeChild(overlay); } }); + // make overlay focusable so keydown works + overlay.tabIndex = -1; overlay.focus(); + // load initial recent results (empty query) + fetch(window.location.origin + '/atlas/search/cases/').then(function(r){ return r.text(); }).then(function(html){ results.innerHTML = html; }).catch(function(){}); + }catch(err){ console.error('openCasePicker error', err); } + } + })(window);