Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

mention-autocomplete.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

mention-autocomplete.tsBlame101 lines · 1 contributor
829a046Claude1/**
2 * @mention autocomplete for comment textareas.
3 *
4 * Injects a floating dropdown when the user types `@` followed by at least
5 * one character. Fetches `/api/users/suggest?q=<prefix>` and renders up to
6 * 8 matching usernames. Arrow-key navigation + Enter/Tab to select.
7 *
8 * Usage: call `mentionAutocompleteScript()` and inject the return value
9 * into a `<script dangerouslySetInnerHTML={{ __html: ... }} />` tag inside
10 * any page that has comment textareas. The script attaches itself to ALL
11 * textareas on the page via event delegation.
12 */
13
14export function mentionAutocompleteScript(): string {
15 return `(function(){
16 var _mc_popup=null,_mc_ta=null,_mc_idx=0,_mc_items=[];
17 var _mc_css=[
18 '.mc-popup{position:fixed;z-index:9999;background:var(--bg-elevated,#1c2128);border:1px solid var(--border,#30363d);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,0.40);overflow:hidden;min-width:180px;max-width:280px;}',
19 '.mc-item{display:flex;align-items:center;gap:8px;padding:8px 12px;font-size:13px;cursor:pointer;color:var(--text,#e6edf3);}',
20 '.mc-item:hover,.mc-item.is-sel{background:var(--bg-hover,#21262d);}',
21 '.mc-item-at{color:var(--text-muted,#8b949e);font-size:12px;}',
22 '.mc-item-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500;}',
23 ].join('');
24 function injectStyle(){if(!document.getElementById('mc-style')){var s=document.createElement('style');s.id='mc-style';s.textContent=_mc_css;document.head.appendChild(s);}}
25 function getCaretCoords(ta){
26 var mirror=document.createElement('div');
27 var cs=getComputedStyle(ta);
28 ['fontFamily','fontSize','fontWeight','letterSpacing','lineHeight','padding','border','boxSizing','whiteSpace','wordWrap','overflowWrap'].forEach(function(p){mirror.style[p]=cs[p];});
29 mirror.style.position='absolute';mirror.style.top='-9999px';mirror.style.left='-9999px';mirror.style.width=ta.offsetWidth+'px';mirror.style.height='auto';
30 var text=ta.value.slice(0,ta.selectionStart);
31 mirror.textContent=text;
32 var span=document.createElement('span');span.textContent='@';mirror.appendChild(span);
33 document.body.appendChild(mirror);
34 var r=ta.getBoundingClientRect();
35 var rect=span.getBoundingClientRect();
36 document.body.removeChild(mirror);
37 return{top:rect.top,left:rect.left};
38 }
39 function showPopup(ta,items,atPos){
40 injectStyle();hidePopup();
41 if(!items.length)return;
42 _mc_ta=ta;_mc_items=items;_mc_idx=0;
43 _mc_popup=document.createElement('div');_mc_popup.className='mc-popup';
44 items.forEach(function(u,i){
45 var d=document.createElement('div');d.className='mc-item'+(i===0?' is-sel':'');
46 d.innerHTML='<span class="mc-item-at">@</span><span class="mc-item-name">'+escHtml(u.username)+'</span>';
47 d.addEventListener('mousedown',function(e){e.preventDefault();insertMention(u.username,atPos);});
48 _mc_popup.appendChild(d);
49 });
50 var coords=getCaretCoords(ta);
51 _mc_popup.style.top=(coords.top+20)+'px';
52 _mc_popup.style.left=coords.left+'px';
53 document.body.appendChild(_mc_popup);
54 }
55 function hidePopup(){if(_mc_popup){_mc_popup.remove();_mc_popup=null;}_mc_ta=null;_mc_items=[];_mc_idx=0;}
56 function setSelection(i){_mc_idx=i;var els=_mc_popup?_mc_popup.querySelectorAll('.mc-item'):[];els.forEach(function(el,j){el.classList.toggle('is-sel',j===i);});}
57 function insertMention(username,atPos){
58 if(!_mc_ta)return;
59 var val=_mc_ta.value;var before=val.slice(0,atPos);var after=val.slice(_mc_ta.selectionStart);
60 var newVal=before+'@'+username+' '+after;
61 _mc_ta.value=newVal;
62 var pos=atPos+username.length+2;
63 _mc_ta.setSelectionRange(pos,pos);
64 hidePopup();
65 _mc_ta.dispatchEvent(new Event('input',{bubbles:true}));
66 }
67 function escHtml(s){return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}
68 var _mc_timer=null;
69 document.addEventListener('input',function(e){
70 var ta=e.target;
71 if(ta.tagName!=='TEXTAREA')return;
72 clearTimeout(_mc_timer);
73 var val=ta.value;var pos=ta.selectionStart;
74 var lastAt=val.lastIndexOf('@',pos-1);
75 if(lastAt===-1){hidePopup();return;}
76 var afterAt=val.slice(lastAt+1,pos);
77 if(/\\s/.test(afterAt)||afterAt.length===0){hidePopup();return;}
78 if(afterAt.length>20){hidePopup();return;}
79 var atPos=lastAt;
80 _mc_timer=setTimeout(function(){
81 fetch('/api/users/suggest?q='+encodeURIComponent(afterAt)).then(function(r){return r.json();}).then(function(data){
82 if(!ta.isConnected){hidePopup();return;}
83 showPopup(ta,data.users||[],atPos);
84 }).catch(function(){hidePopup();});
85 },120);
86 });
87 document.addEventListener('keydown',function(e){
88 if(!_mc_popup)return;
89 if(e.key==='ArrowDown'){e.preventDefault();setSelection(Math.min(_mc_idx+1,_mc_items.length-1));}
90 else if(e.key==='ArrowUp'){e.preventDefault();setSelection(Math.max(_mc_idx-1,0));}
91 else if(e.key==='Enter'||e.key==='Tab'){
92 if(_mc_popup){e.preventDefault();insertMention(_mc_items[_mc_idx].username,_mc_popup._atPos||0);
93 // re-read atPos from last known
94 var u=_mc_items[_mc_idx];if(u&&_mc_ta){var val=_mc_ta.value;var p=_mc_ta.selectionStart;var lastAt=val.lastIndexOf('@',p-1);insertMention(u.username,lastAt);}}
95 }
96 else if(e.key==='Escape'){hidePopup();}
97 });
98 document.addEventListener('click',function(e){if(_mc_popup&&!_mc_popup.contains(e.target))hidePopup();});
99 document.addEventListener('blur',function(e){if(e.target.tagName==='TEXTAREA')setTimeout(function(){if(_mc_popup&&!_mc_popup.matches(':hover'))hidePopup();},150);},true);
100})();`;
101}