diff --git a/static/sessions.js b/static/sessions.js index ce28e5b..6bb6cd8 100644 --- a/static/sessions.js +++ b/static/sessions.js @@ -287,7 +287,8 @@ function renderSessionListFromCache(){ trash.onclick=async(e)=>{e.stopPropagation();e.preventDefault();await deleteSession(s.session_id);}; // Project move button (folder icon) const move=document.createElement('button'); - move.className='session-action-btn'+(s.project_id?' has-project':'');move.innerHTML='📂';move.title='Move to project'; + move.className='session-action-btn'+(s.project_id?' has-project':''); + move.innerHTML='📂';move.title='Move to project'; move.onclick=async(e)=>{e.stopPropagation();e.preventDefault();_showProjectPicker(s,move);}; // Project dot indicator if(s.project_id){ @@ -360,15 +361,13 @@ function _showProjectPicker(session, anchorEl){ document.querySelectorAll('.project-picker').forEach(p=>p.remove()); const picker=document.createElement('div'); picker.className='project-picker'; - // Close on outside click - const close=(e)=>{if(!picker.contains(e.target)&&e.target!==anchorEl){picker.remove();document.removeEventListener('click',close);}}; // "No project" option const none=document.createElement('div'); none.className='project-picker-item'+(!session.project_id?' active':''); none.textContent='No project'; none.onclick=async()=>{ - document.removeEventListener('click',close); picker.remove(); + document.removeEventListener('click',close); await api('/api/session/move',{method:'POST',body:JSON.stringify({session_id:session.session_id,project_id:null})}); session.project_id=null; renderSessionListFromCache(); @@ -389,8 +388,8 @@ function _showProjectPicker(session, anchorEl){ name.textContent=p.name; item.appendChild(name); item.onclick=async()=>{ - document.removeEventListener('click',close); picker.remove(); + document.removeEventListener('click',close); await api('/api/session/move',{method:'POST',body:JSON.stringify({session_id:session.session_id,project_id:p.project_id})}); session.project_id=p.project_id; renderSessionListFromCache(); @@ -398,19 +397,21 @@ function _showProjectPicker(session, anchorEl){ }; picker.appendChild(item); } - // "+ New project" item + // "+ New project" shortcut at the bottom const createItem=document.createElement('div'); createItem.className='project-picker-item project-picker-create'; createItem.textContent='+ New project'; createItem.onclick=async()=>{ picker.remove(); document.removeEventListener('click',close); + // Prompt for name inline const name=prompt('Project name:'); if(!name||!name.trim()) return; const color=PROJECT_COLORS[_allProjects.length%PROJECT_COLORS.length]; const res=await api('/api/projects/create',{method:'POST',body:JSON.stringify({name:name.trim(),color})}); if(res.project){ _allProjects.push(res.project); + // Now move session into it await api('/api/session/move',{method:'POST',body:JSON.stringify({session_id:session.session_id,project_id:res.project.project_id})}); session.project_id=res.project.project_id; await renderSessionList(); @@ -418,11 +419,13 @@ function _showProjectPicker(session, anchorEl){ } }; picker.appendChild(createItem); - // Position picker on document.body to avoid overflow:hidden clipping + // Append to body and position using getBoundingClientRect so it isn't clipped + // by overflow:hidden on .session-item ancestors document.body.appendChild(picker); const rect=anchorEl.getBoundingClientRect(); picker.style.position='fixed'; picker.style.zIndex='999'; + // Prefer opening below; flip above if too close to bottom of viewport const spaceBelow=window.innerHeight-rect.bottom; if(spaceBelow<160&&rect.top>160){ picker.style.bottom=(window.innerHeight-rect.top+4)+'px'; @@ -431,10 +434,13 @@ function _showProjectPicker(session, anchorEl){ picker.style.top=(rect.bottom+4)+'px'; picker.style.bottom='auto'; } - const pickerW=160; + // Align right edge of picker with right edge of button; keep within viewport + const pickerW=Math.min(220,Math.max(160,picker.scrollWidth||160)); let left=rect.right-pickerW; if(left<8) left=8; picker.style.left=left+'px'; + // Close on outside click + const close=(e)=>{if(!picker.contains(e.target)&&e.target!==anchorEl){picker.remove();document.removeEventListener('click',close);}}; setTimeout(()=>document.addEventListener('click',close),0); } diff --git a/static/style.css b/static/style.css index 2691cb3..3eeccbf 100644 --- a/static/style.css +++ b/static/style.css @@ -540,7 +540,7 @@ body.resizing{user-select:none;cursor:col-resize;} .project-create-btn{font-size:10px;padding:3px 6px;border-radius:12px;cursor:pointer;border:1px dashed var(--border2);background:none;color:var(--muted);opacity:.6;transition:all .15s;} .project-create-btn:hover{opacity:1;border-color:var(--blue);color:var(--blue);} .project-create-input{font-size:10px;padding:3px 8px;border-radius:12px;border:1px solid rgba(124,185,255,.6);background:rgba(20,32,60,.9);color:var(--text);outline:none;width:100px;font-family:inherit;box-shadow:0 0 0 2px rgba(124,185,255,.15);} -.project-picker{position:absolute;right:0;top:100%;background:var(--sidebar);border:1px solid var(--border2);border-radius:8px;padding:4px;z-index:30;min-width:140px;max-width:220px;width:max-content;box-shadow:0 4px 16px rgba(0,0,0,.3);} +.project-picker{position:absolute;right:0;top:100%;background:var(--sidebar);border:1px solid var(--border2);border-radius:8px;padding:4px;z-index:30;min-width:160px;max-width:220px;width:max-content;box-shadow:0 4px 16px rgba(0,0,0,.3);} .project-picker-item{padding:5px 10px;font-size:11px;border-radius:6px;cursor:pointer;color:var(--muted);transition:all .1s;display:flex;align-items:center;gap:6px;} .project-picker-item:hover{background:rgba(255,255,255,.08);color:var(--text);} .project-picker-item.active{color:var(--blue);}