1(function(){ 2function shmaEscHtml(s){var d=document.createElement("div");d.textContent=s;return d.innerHTML;} 3function shmaEscAttr(s){return String(s).replace(/&/g,"&").replace(/"/g,""");} 4function shmaMentionJsonOr401(r){ 5 if(r.status===401){ 6 window.__shmaApi401=1; 7 return Promise.reject(); 8 } 9 if(!r.ok)return Promise.reject(); 10 window.__shmaApi401=0; 11 return r.json(); 12} 13window.shmaMentionAttach=function(textarea, opts){ 14 if(!textarea||!opts||!opts.api||!opts.csrf)return; 15 var api=opts.api, csrf=opts.csrf; 16 var scope=opts.scope||"room"; 17 var threadId=+opts.threadId||0; 18 var wrap=textarea.closest(".composer-fields")||textarea.parentElement; 19 if(!wrap)return; 20 var pop=document.createElement("div"); 21 pop.className="shma-mention-pop"; 22 pop.setAttribute("role","listbox"); 23 pop.setAttribute("hidden",""); 24 wrap.insertBefore(pop, wrap.firstChild); 25 var sel=0, rows=[], tmr=null; 26 function parse(){ 27 var v=textarea.value, c=textarea.selectionStart|0; 28 var before=v.slice(0,c); 29 var m=before.match(/@([a-zA-Z0-9_]*)$/); 30 if(!m)return null; 31 return {start:c-m[0].length, query:m[1]}; 32 } 33 function hide(){pop.setAttribute("hidden","");pop.innerHTML="";rows=[];} 34 function pos(){ 35 if(pop.hasAttribute("hidden"))return; 36 var rect=textarea.getBoundingClientRect(); 37 var w=Math.min(Math.max(rect.width,220),400); 38 pop.style.position="fixed"; 39 pop.style.left=Math.max(8,rect.left)+"px"; 40 pop.style.width=w+"px"; 41 var h=pop.offsetHeight||160; 42 var top=rect.top-h-6; 43 if(top<8)top=rect.bottom+6; 44 if(top+h>window.innerHeight-8)top=Math.max(8,window.innerHeight-h-8); 45 pop.style.top=top+"px"; 46 } 47 function render(){ 48 sel=0; 49 if(!rows.length){hide();return;} 50 pop.innerHTML=rows.map(function(u,i){ 51 var av=u.avatar_url?'<img src="'+shmaEscAttr(u.avatar_url)+'" alt="" width="28" height="28" loading="lazy" referrerpolicy="no-referrer">':'<span class="shma-mention-pop__ph"></span>'; 52 return '<button type="button" class="shma-mention-pop__row'+(i===0?' is-active':'')+'" data-i="'+i+'">'+av+'<span class="shma-mention-pop__meta"><span class="shma-mention-pop__name">'+shmaEscHtml(u.display_name||u.login)+'</span><span class="shma-mention-pop__login">@'+shmaEscHtml(u.login)+'</span></span></button>'; 53 }).join(""); 54 pop.removeAttribute("hidden"); 55 requestAnimationFrame(pos); 56 } 57 function selUpd(){ 58 var btns=pop.querySelectorAll(".shma-mention-pop__row"); 59 for(var i=0;i<btns.length;i++)btns[i].classList.toggle("is-active",i===sel); 60 } 61 function applyAt(u){ 62 var p=parse(); 63 if(!p||!u)return; 64 var v=textarea.value, c=textarea.selectionStart|0; 65 var before=v.slice(0,p.start); 66 var after=v.slice(c); 67 var ins="@"+u.login+" "; 68 textarea.value=before+ins+after; 69 var caret=before.length+ins.length; 70 textarea.focus(); 71 textarea.setSelectionRange(caret,caret); 72 hide(); 73 textarea.dispatchEvent(new Event("input",{bubbles:true})); 74 } 75 function fetchQ(q){ 76 if(tmr)clearTimeout(tmr); 77 tmr=setTimeout(function(){ 78 fetch(api+"?action=shma_api",{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json"},body:JSON.stringify({sub:"mention_suggest",q:q,scope:scope,thread_id:threadId,csrf:csrf})}) 79 .then(shmaMentionJsonOr401) 80 .then(function(d){ 81 var cur=parse(); 82 if(!cur||cur.query!==q)return; 83 rows=(d&&d.ok&&d.users)?d.users:[]; 84 render(); 85 requestAnimationFrame(pos); 86 }).catch(function(){}); 87 },170); 88 } 89 function onInput(){ 90 var p=parse(); 91 if(!p){hide();return;} 92 if(scope==="room"&&p.query.length===0){hide();return;} 93 fetchQ(p.query); 94 } 95 textarea.addEventListener("input",onInput); 96 textarea.addEventListener("keyup",function(){requestAnimationFrame(pos);}); 97 textarea.addEventListener("click",function(){setTimeout(onInput,0);}); 98 pop.addEventListener("mousedown",function(e){ 99 var b=e.target&&e.target.closest?e.target.closest(".shma-mention-pop__row"):null; 100 if(!b)return; 101 e.preventDefault(); 102 var i=parseInt(b.getAttribute("data-i"),10);
103 if(rows[i])applyAt(rows[i]); 104 }); 105 window.addEventListener("resize",function(){requestAnimationFrame(pos);}); 106 document.addEventListener("mousedown",function(e){ 107 if(pop.hasAttribute("hidden"))return; 108 if(e.target===textarea||textarea.contains(e.target)||pop.contains(e.target))return; 109 hide(); 110 },true); 111 textarea.addEventListener("keydown",function(e){ 112 if(pop.hasAttribute("hidden")||!rows.length)return; 113 if(e.key==="ArrowDown"){e.preventDefault();e.stopPropagation();sel=(sel+1)%rows.length;selUpd();return;} 114 if(e.key==="ArrowUp"){e.preventDefault();e.stopPropagation();sel=(sel+rows.length-1)%rows.length;selUpd();return;} 115 if(e.key==="Enter"&&!e.shiftKey&&!e.isComposing){e.preventDefault();e.stopPropagation();if(rows[sel])applyAt(rows[sel]);return;} 116 if(e.key==="Escape"){e.preventDefault();e.stopPropagation();hide();return;} 117 },true); 118}; 119})();
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.